title 'DSKDRIVE foreign disk driver - using RSX (85/02/02)' subttl 'default IBM CPM86 DSDD on Kaypro 4' ; .z80; for M80 benefit ver equ 13; Version number to be filled in. <10 debug ; false equ 0 true equ NOT false ; ; customise the @RSX value for the application. See BDOS call lists ; below for guidance. Try to avoid values likely to create conflicts @RSX equ 115; 115 is GSX graphics on CPM86, probably safe driver equ true; TRUE installs drivers visible to DOS ; ; Copyright (c) 1985 by C.B Falconer (203) 281-1438 ; 680 Hartford Tpk, Hamden, Ct 06504. All rights reserved. ; ; This program may be copied/used/modified etc, but it may NOT ; be sold without express written permission from C.B. Falconer ; ; This system provides a foreign disk driver on the Kaypro 4 ; which can then be configured to any suitable format. The ; default format is that for the IBM PC running CPM86 (DSDD). ; Once installed (by running) the drive configuration can be ; altered by auxiliary software (still to be written). To remove ; the driver run the program again. The default foreign drive ; type can easily be altered, once the paramaters are known. ; ; The system uses the generalized CPM 2.2 RSX system published ; and documented separately, and whose source code is embodied ; within this program. The program uses Z80 opcodes only in the ; actual disk driver (for hardware reasons) and in the transfer ; between user storage and disk buffer (for performance). This ; should ease porting to 8080 only systems. ; ; NOTE: This source file requires SLRs Z80ASM or M80 for assembly, ; and RELOCCP.SYS for run-time load and relocation. M80 has ; not been checked. I suspect some labels may be too long. ; ; Revisions: (in LIFO order) ; 1.3 85/5/15. Revised alternate table for Kaypro 2 SSDD. No code ; changes whatsoever. ; 1.2 85/2/3. Added support for single density. DOS dskreset call ; resets this drive. SECSHF calculated from HSTBLK. ; 1.1 85/2/1. Altered configuration table to reduce portion needed ; in external cookbook files for reconfiguration. A | B | C ; runtime parameters for alternate drive type selection added, ; so that a total of 5 configuration (including native) are ; available without the auxiliary ALTDRIVE program. ; 1.0 85/01/30. Initial release. Alteration software needed ; 0.3 85/01/27. First working disk drive. ; 0.2 85/01/24. Provisions for BDOS driver installation, ; checks etc. ; 0.1 85/01/05. Inactive causes reversion to original BDOS call in ; case this was a substitute for it. debug allows testing. ; Keep track of current DMA address. ; 0.0 Original version, by C.B. Falconer (85/Jan/04). ; ; WARNING - When an RSX is installed all warm boots are converted ; into "returns" to the original calling CCP, which is not removed ; nor reloaded. The original Digital Research CCP will not start ; executing SUBMIT jobs under these circumstances. Installation ; of CCPLUS will repair this omission. ZCPR action has not been ; checked. Other problems (with the original CCP) may appear on ; "select errors", i.e. by logging into a non-existent drive. ; ; This system is the outline of a generalized RSX system, with ; initialization and termination code. It is expected to ; respond to BDOS calls with the value @RSX (normally larger ; than any usual CPM call value), and receive an argument of ; some form in the (de) register. Two special values of (de) ; are reserved (0 and -1). In usual operation the (de) value ; is normally a pointer, which cannot take on either of these ; special values. For character output RSXs (e.g. list output ; replacement) this prevents sending a nul or a rubout with all ; bits set. Remember this is a full word parameter. ; ; 0 implies a residence inquiry, and should return a zero ; value if the system is not resident, (as will CPM 2.2 for any ; invalid calls), and non-zero if resident and activated. This ; allows the initialization code to detect that the system is ; already mounted, and avoid multiple loading. Similarly ; application programs can check that the RSX is available. A ; zero value returned in (a) signifies the RSX is not available. ; ; The second special value is 0ffffh (i.e. -1), which ; signals the system to become inactive, so that the next ; warm boot will remove it entirely. ; ; A third special argument is 0001h. This is optional, and is ; used to return a data address. For DSKDRIVE in particular ; this is used to return a pointer to the memory resident ; drive configuration table (whose structure thus should not ; be altered in order to maintain compatibility with auxiliary ; software). DSKDRIVE actually returns this pointer for all ; DE arguments other than 0 or -1, but this should not be ; counted on. ; ; This file implements the DSKDRIVE RSX, and indicates where the ; code should be installed, and the calling conventions for other ; RSXs. See the label "chkparms" to allow initialization ; parameters, and the label "rsx" to install the actual code. ; Note especially than any code located ahead of the label ; "@keep" is available only during initialization. However ; the initialization section can call routines in the retained ; portion. The "bthoot" connector allows re-initialization on ; each "warm boot". ; ; The system, when combined with "RELOCCP", loads itself just ; below the CCP, and keeps the CCP resident. (See RELOCCP.OVR, ; which is an assembly time option for RELOCCP, for a method ; of minimizing memory wastage for multiple RSX's). If RSX.COM ; is executed while resident, it attempts to remove itself and ; reclaim memory. If other systems have been loaded below RSX ; it will not be able to reclaim memory until those systems ; (probably further RSX's) have been removed. ; ; To create a custom RSX modify the equate for "@RSX" above, ; and create any necessary code in the "rsx" and "chkparms" ; areas. Give the resultant file an approriate name (e.g. MYRSX), ; assemble, and create the MYRSX.COM with RELOCCP (See RELOCCP ; documentation). When executed you now have available a new ; BDOS function, which executes whatever you put in. ; ; Acknowledgement: Some of the mechanisms used in this system ; have been taken from various public domain systems, especially ; those by Gary Novasielski and Bruce Ratoff. ; ; This system was created to ease installation of two operations: ; a generalized foreign disc driver, and a "CHAIN" mechanism ; whereby programs can load and execute arbitrary commands. The ; pre-existing KEYS program can probably be converted to execute ; under this system, as can UNSPOOL40 (enhancement of Br. Ratoffs ; UNSPOL30). ; ; While some code economies could easily be made for any particular ; application, I feel that using one standard environment is much ; more important. Thus the provisions for bios driver modification ; have been installed, with the complete check/restore system. The ; disadvantage remains that custom code must be included in this ; source and assembled, rather than standing by itself. ; ; BDOS Functions: @SYS equ 0 @KEY equ 1 @CON equ 2 @RDR equ 3 @PUN equ 4 @LST equ 5 @DIO equ 6 @RIO equ 7 @SIO equ 8 @MSG equ 9 @INP equ 10 @RDY equ 11 @VER equ 12 @LOG equ 13 @DSK equ 14 @OPN equ 15 @CLS equ 16 @DIR equ 17 @NXT equ 18 @DEL equ 19 @FRD equ 20 @FWR equ 21 @MAK equ 22 @REN equ 23 @LGV equ 24 @CUR equ 25 @DMA equ 26 @ALO equ 27 @WPT equ 28 @GRO equ 29 @CHG equ 30 @GPM equ 31 @USR equ 32 @RRD equ 33 @RWR equ 34 @SIZ equ 35 @REC equ 36 @RDV equ 37 @WRZ equ 40; 38 and 39 are MPM functions ; ; some CPM 3.0 functions, for reference. Match function if ; possible when a 2.2 RSX is created. ; 41 thru 50 are used @CHN equ 47; Chain to command line in (dmaadr) @OVL equ 59; Load overlay ;@RSX equ 60; Call RSX. Different usage than here ; 98 thru 112 are used. 115 is CPM86 GSX graphics. @PFN equ 152; parse filename ; ; System equates: CPMBASE equ 0 boot equ CPMBASE bdos equ boot+5 tfcb equ boot+5CH tfcb1 equ tfcb tfcb2 equ tfcb+16 tbuff equ boot+80H TPA equ boot+100H CTRL equ ' '-1; CTRL CHAR MASK CR equ CTRL AND 'M' LF equ CTRL AND 'J' TAB equ CTRL AND 'I' FF equ CTRL AND 'L' BS equ CTRL AND 'H' eof equ CTRL AND 'Z' fcblen equ 36; Length of input FCB ; ; nvects is larger than the CPM 2.2 standard to allow for: ; 1. CPM 3 bios system ; 2. Other systems with extended bios calls and/or use ; e.g. Kaypro has a key table, which some applications ; alter (via the pointer at 1). If the space is not ; reserved system crashes will result. nvects equ 29; Number of BIOS vectors n22vec equ 16; Number of CPM 2.2 vectors ; ; Macro Definitions ; ; Force location counter zero modulo val ; with respect to "relative" by filling with "fill" bytes align macro val,relative,fill local here here equ $-relative if (here+val)/val*val-here-val if nul fill ds (here+val)/val*val-here else;; Z80ASM specific coding. ds (here+val)/val*val-here,fill endif endif endm ; ; ************************************************************ ; * This is my standard page relocatable system. See RELOCCP * ; ************************************************************ ; cseg; system requires org 0 and org 100h modules ; You may replace this with two "orgs" in two ; different assemblies to get the overall system ; for RELOCCP to prepare and relocate at run-time. ; ; This defines the first location of the relocated image. ; The portion from here to "@keep" is not retained after ; initialization. The data here is used for relocation @base: jp intro; Following is std relocation data @size: dw segsiz; size of segment to relocate @memsz: db pages; total memory use in pages ; ; Patch/alter this value to non-zero if the RSX may only be ; loaded in a virgin system. This should be done if the system ; installs bios modifications that the BDOS can see. chkflg: db driver; Set non-zero for virgin system only ; ; This code gets everything started intro: ld hl,(bdos+1); First so other calls work ld (gobdos+1),hl; Save the BDOS entry point ld de,signon call tstr call chkparms; Do any parameter checking needed jp c,exeunt; ..with help message on carry=fault ld de,0 ld a,@RSX; call DOS; enquire whether loaded or a; If loaded, jp nz,intro1; then bring it down call rsxsetup; initialize. call init; Customized portion jp bootrq; ..bootrq, which sets connectors ; ; already loaded, bring it down intro1: ld de,-1 ld a,@RSX call DOS; tell it to come down on next boot jp boot; which should kill it ; ; Setup the system. First, check environment to see if ; BIOS vectors are accessible and reasonable. rsxsetup: ld a,(boot); Location BOOT should cp 0c3h; have a JMP instruction jp nz,vecterr ld hl,(boot+1); Location one points to ex de,hl; the table of bios jumps ld hl,(bdos+1); This should point to BDOS ld a,(chkflg) ld (kill+1),a; Save for use at exit time. Unclean or a; but want it user patchable (1 place) call nz,chksys; Hook, prevent loading invalid system ex de,hl ld c,nvects; preserve z flag in here ld de,biosv push de; by default init our vector jp nz,vecterr; after push, to correct stack ld de,bsave; which we move into ex de,hl; the code. rsxsetup1: ld a,nvects-n22vec cp c jp nc,rsxsetup2; stop checking after 2.2 types ld a,(de) xor (hl); another JMP? and 0f1h; allow Call or Jmp jp nz,vecterr rsxsetup2: ld a,(de) ld (hl),a; bsave[n,0] inc de inc hl ex (sp),hl inc hl ld a,(de) ld (hl),a; biosv[n,1] inc hl ex (sp),hl ld (hl),a; bsave[n,1] inc de inc hl ld a,(de) ld (hl),a; bsave[n,2] ex (sp),hl ld (hl),a; biosv[n,2] inc hl; biosv[n+1,0] ex (sp),hl inc hl; biosv[n+1,0] inc de dec c; n := n+1; jp nz,rsxsetup1 pop hl; purge 2nd transfer address ; This was satisfactory, now if "driver" is TRUE, patch the ; BIOS drivers to point to the new copy (only the std CPM 2.2 ; entries) so that new entries can be made locally. The final ; exit mechanism will restore everything. ld a,(chkflg) or a call nz,bpatch ; Save old vectors and CCP return address. BDOS saved at the very ; beginning of system. User code can patch new vectors as required. ld hl,(boot+1) ld (boot0+1),hl; Save the BOOT vector ld hl,2; Retrieve the CCP add hl,sp; return address from ld a,(hl); down the stack a ways. inc hl ld h,(hl) ld l,a ld (ccpret+1),hl; Save the CCP re-entry point ret ; ; Patch the original bios drivers, which have been checked ; for validity and a reasonable location, to point to the ; new bios table. Only called when "driver" is TRUE. DO NOT ; PATCH the original warm/cold boot entries. bpatch: ld hl,(boot+1) inc hl inc hl ld de,biosv ld c,n22vec-1; count of 2.2 bios entries only bpat1: inc hl inc de inc de inc de inc hl ld (hl),e inc hl ld (hl),d dec c jp nz,bpat1; more ret ; ; Check system is in usable state. (de) holds the bios pointer, ; and (hl) holds the bdos pointer. If the values are not ; reasonable, return non-zero flag. ; This routine has no relocated code, so it may be patched. ; a,f,h,l chksys: ld a,e cp 3 ret nz; boot pointer should end in 3 add e sub l ret nz; bdos pointer should end in 6 ld a,d sub h sub 0eh ret nz; Page difference should be 0eh ld a,(hl) sub 0c3h; JMP instruction ret nz inc hl ld a,(hl) sub 011h ret nz; bdos destination should go to 11 inc hl ld a,h sub (hl); on same page to be valid ret ; vecterr: pop de; remove the extra pointer ld de,vcterrmsg ; " " ; exit with message de^ exeunt: call tstr jp boot; try re-booting. ; vcterrmsg: db CR,LF,'Invalid system$' ; ; ============================================================= ; **** Custom portion of initialization code here **** ; ============================================================= ; ; Check run command parameters. Carry if not satisfactory ; This version for DSKDRIVE moves alternate configurations into ; place on "DSKDRIVE A", "DSKDRIVE B" or "DSKDRIVE C" commands. chkparms: ld hl,alta ld a,(tfcb+1) sub 'A'; fcbs are always upshifted jp z,chkp1; A, go move it in dec a or a; clear any carry ld hl,altb jp z,chkp1; B dec a ret nz; not C ld hl,altc chkp1: ex de,hl; move configuration (hl) into ld hl,info; "info" area ld b,infosz chkp2: ld a,(de) ld (hl),a inc de inc hl dec b jp nz,chkp2 ret ; ; Custom initialization. ; When this routine is reached the various bios vector copies ; (and patches if "driver" is true) have been made. This ; routine may alter the connectors in "biosv" to install new ; drivers, etc. The original routines are available thru ; "bsave" table. Remember that any routines connected MUST ; live in the retained portion of code, following "@keep" ; below, and I recommend putting their code in the "rsx" area. ; If "driver" is false any bios modifications made through ; this routine will only be available to applications calling ; the bios directly (through location 1), and not to BDOS. init: ld de,home ld a,8 call apatch ld de,seldsk ld a,9 call apatch ld de,setrk ld a,10 call apatch ld de,setsec ld a,11 call apatch ld de,setdma ld a,12 call apatch ld de,read ld a,13 call apatch ld de,write ld a,14 call apatch ld de,sectran ld a,16 ; " " ; Patch bios connector (a) to connect to (de). Note that ; the 0th connector is the cold boot entry (per DR standards). ; Also note that the first entry in biosv is entry # 1 ; DO NOT patch connectors 0 or 1, nor any above "n22vecs" ; a,f apatch: dec a dec a push hl ld l,a add a add l; *3 ld hl,biosv+4; offset past "jmp" opcode add l ld l,a adc h sub l ld h,a ld (hl),e inc hl ld (hl),d pop hl ret ; signon: db 'DSKDRIVE [A|B|C] (an RSX) ' db ver / 10 + '0', '.', ver MOD 10 + '0', '$' ; ; Alternate drive configurations, used on "DSKDRIVE A" ; or "DSKDRIVE B" commands. No parameter uses default. ; see "info" table for for parameter meanings. You may ; reconfigure these tables for your most used types, thus ; allowing up to five (including native) drive versions. ; alta: dw 40; cpmspt db 3; bsh db 07h,0; blm,exm dw 194; dsm dw 63; drm db 0F0h,0; al0,al1 dw 16; cks dw 1; off db 4; hstblk db 0, 1, 2, 3, 4, 5, 6, 7; sxltbl db 8, 9,10,11,12,13,14,15 db 16,17,18,19,20,21,22,23 db 24,25,26,27,28,29,30,31 db 32,33,34,35,36,37,38,39 db 40,41,42,43,44,45,46,47 db 48,49,50,51,52,53,54,55 db 56,57,58,59,60,61,62,63 db 64,65,66,67,68,69,70,71 db 72,73,74,75,76,77,78,79 db 0; spare db 0; sec1st db 0; config db 40; ntrks db 0; nsecs db 'KAYPRO 2 (SSDD)$$$$$$$$$$$$' ; altb: dw 36; cpmspt db 4; bsh db 0Fh,0; blm,exm dw 170; dsm dw 63; drm db 080h,0; al0,al1 dw 16; cks dw 4; off db 2; hstblk db 0, 1, 2, 3, 4, 5, 6, 7; sxltbl db 8, 9,10,11,12,13,14,15 db 16,17,18,19,20,21,22,23 db 24,25,26,27,28,29,30,31 db 32,33,34,35,36,37,38,39 db 40,41,42,43,44,45,46,47 db 48,49,50,51,52,53,54,55 db 56,57,58,59,60,61,62,63 db 64,65,66,67,68,69,70,71 db 72,73,74,75,76,77,78,79 db 0; spare db 1; sec1st db 2; config db 40; ntrks db 0; nsecs db 'TS802 (Televideo DSDD)$$$$$' ; altc: dw 32; cpmspt db 3; bsh db 07h,0; blm,exm dw 251; dsm dw 127; drm db 0F0h,0; al0,al1 dw 32; cks dw 3; off db 2; hstblk db 0, 1, 2, 3, 4, 5, 6, 7; sxltbl db 8, 9,10,11,12,13,14,15 db 16,17,18,19,20,21,22,23 db 24,25,26,27,28,29,30,31 db 32,33,34,35,36,37,38,39 db 40,41,42,43,44,45,46,47 db 48,49,50,51,52,53,54,55 db 56,57,58,59,60,61,62,63 db 64,65,66,67,68,69,70,71 db 72,73,74,75,76,77,78,79 db 0; spare db 1; sec1st db 2; config db 40; ntrks db 0; nsecs db 'HP125 (DSDD)$$$$$$$$$$$$$$$' ; ; ; ------------------ End custom initialization area ---------------- ; align 256,@base,0; ensure page aligned ; ; ******************************************************* ; * The code from here up is retained in memory after * ; * initialization. It may be used by the initializer * ; * This code MUST start on a page boundary. * ; ******************************************************* @keep: ; ; During operation, this location will point to intercept and will ; be jumped to by BDOS calls from location 5. This organization ; depends on the fact that BDOS calls the bios directly (ignoring ; the pointer at location 1), except when needing a warm boot ; (e.g. after a disk error), when it uses the pointer at 1. ; ; This must be at the lowest location in the protected code segment. bdosv: jp intercept; the bdos link ; ; This area replaces the bios jump table, allowing intercepts. ; Any intercepts (usually console commands) are set up by the ; initializing code, which is discarded after execution. See ; comments on "nvects" above. Normally these bios vectors will be ; accessed only by executing programs, and not by the BDOS unit. ; To insert drivers in the bios it is necessary to save the original ; bios pointers (for restoration when the RSX is removed) and ; install jumps to this revised table in the original table. Note ; that the original table may contain a CALL for the warmboot vector, ; so that ROM based code can tell the CPM system size when called. biosv: REPT nvects jp $-$ endm ; ; This table has two purposes - it allows connection to the original ; bios vectors destinations, and it saves all the values for ; restoration upon RSX exit. Thus the custom initialization section ; can patch the real bios table to install new disk drivers, etc. ; Note that this can only work when this is the FIRST rsx installed. ; Further ones can only intercept user bios calls and BDOS calls. ; Provided that the bios table in effect on entry to this system ; does not hold any data areas, the restoration on RSX removal will ; be harmless. bsave: REPT nvects jp $-$ endm ; ; The "old" entries needed. Index 0 here is warmboot, not cold @home equ bsave+(7*3) @seldk equ bsave+(8*3) @setrk equ bsave+(9*3) @setsec equ bsave+(10*3) @setdma equ bsave+(11*3) @read equ bsave+(12*3) @write equ bsave+(13*3) @sectran equ bsave+(15*3) ; ; This routine intercepts all BDOS calls. ; Note that the initial code sequence allows applications to ; find the actual BDOS, if necessary. See SD88F6 for one use. intercept: ld a,0ffh cp c jp c,gobdos; Never taken, but SD trackable ld a,@SYS; Get function cp c jp z,sysreq; a reboot request ld a,@DMA cp c jp z,dodma ld a,@LOG cp c jp z,drvreset; reset this drive also ld a,@RSX; defined for this system cp c jp nz,gobdos; not for us ; DO NOT optimize the above sequence. It is intended to be ; trackable by utilities to list the RSX's active. The series ; stops when the instruction is not "ld a,bytevalue". The ; initial "ld a,0ffh" will not be counted by tracking utilities ; and the checks on @SYS and @DMA are always expected. This ; organization allows a range of BDOS calls to be intercepted. ; " " ld hl,0 add hl,sp ld sp,lclstk push hl; save entry stack pointer call doit; This is an extension call ex de,hl; save return value pop hl ld sp,hl ex de,hl; return its result ld a,l; copy result to (a) ld b,h; so (ba)=(hl), like BDOS ret; to the caller. ; ; Reset this drive also when application wants a drive reset ; Added to RSX system for disk drive application. drvreset: push bc call bthook pop bc jp gobdos ; ; Keep track of the current DMA address on general principles dodma: ex de,hl ld (dmadr),hl; BDOS' view, not BIOS ex de,hl ; " " ; Connects to the "real" BDOS routine gobdos: jp $-$; Patched on entry ; ; The RSX (resident system extension). Argument is de as usual, ; and the extension specified is (c). Return value is put in hl. ; The stack is already set to the local stack, with the old ; stack pointer under the return from doit. ; a,f,b,c,d,e,h,l (allowed) doit: ld hl,(active); set "enquiry" return value in (l) ld a,l or a jp z,gobdos; inactive, use bdos call ld h,0 ld a,e; check for "loaded enquiry" or d ret z; signal active on enquiry ld a,e and d inc a jp nz,rsx; not "pulldown" request. ; " " ; Inactivate the RSX. Next boot will try to recover the memory. kill: ld a,$-$; patched with "chkflg" at init or a call nz,unpch; Remove any bios alterations xor a ld (active),a; mark inactive ld l,a; return 0, pulldown accepted ld h,a; and we are now inactive. ret ; ; Remove any bios patches made on initialization, in case this is a ; driver being inactivated, and further RSX's are loaded beyond it. ; Thus a "kill" request will be logically executed, even though the ; memory is not reclaimed. However bios alterations via the pointer ; at 1 cannot be removed until a warm boot occurs (that pointer may ; be pointing to an RSX installed later). Thus the table at biosv ; must be updated to point to the original bios entries unpch: ld de,bsave+3 ld hl,(boot0+1) inc hl inc hl inc hl; start at the constat entry push hl ld hl,biosv+3 ld c,n22vec-1; dont alter the warm boot entries unpch1: ld b,l; offset, depends on page alignment inc de; past opcode inc hl; Ignore the opcode (dont change) ld (hl),b; biosv[n,1] --> oldbios[n,0] inc hl ex (sp),hl ld b,h; oldbios page inc hl ld a,(de); lsb ld (hl),a; oldbios[n,1] inc de; ^msb inc hl ld a,(de); msb ld (hl),a; oldbios[n,2] inc hl; oldbios[n+1,0] ex (sp),hl ld (hl),b; biosv[n,2] --> oldbios[n,0] inc hl; biosv[n+1,0] inc de; next opcode dec c; n := n+1 jp nz,unpch1 pop hl ret ; ; Note that the following "boot" entries will never be reached if a ; further RSX has been installed, since that RSX will intercept the ; boot and do a direct return to the CCP (unless it is inactive, and ; removes itself, when a whole chain of RSX removals can be started) ; ; The application process has requested a warm-boot by invoking BDOS ; function 0. If the system is inactive remove it, otherwise return ; to the CCP via the stored (on initialization) CCP return value. sysreq: jp bootrq; This allows separation if needed ; ; The application process has requested a reboot by jumping to ; location 0. If we are no longer active, we will honor the request ; by executing the address found in the BOOT vector at entry. ; Otherwise return to CCP without rebooting. bootrq: ld sp,lclstk; set up a valid stack ld a,(active) or a jp z,done; Not active, all done. Remove self call bthook; Application specific boot action ld c,@LOG call gobdos; Reset drives, like any ld de,actmsg; other reboot does. call tstr ld de,altid; added message portion for call tstr; DSKDRIVE use ; " " ; Reset the system pointers to use this system. ld hl,bdosv ld (bdos+1),hl ld hl,bootrq ld (biosv+1),hl ld hl,biosv ld (boot+1),hl ld hl,tbuff ld (dmadr),hl; Will be set by CCP ccpret: jp $-$; Patched on startup ; ; Done with the system done: ld de,donemsg; Message and jump to old boot addr. call tstr; as originally read from memory wd 1 boot0: jp $-$; Reboot. Patched on initialization ; This value points to old bios table ; ; ================================================================ ; Utility routine area ; ================================================================ ; ; Put string (de) to console, '$' terminated tstr: ld a,@MSG ; " " ; dos call (a) without disturbing registers. Does not use the RSX DOS: push bc push de push hl ld c,a call gobdos pop hl pop de pop bc ret ; ; ================================================================== ; **** To keep as much as possible constant, put real RSX here **** ; ================================================================== ; ; This routine MUST be supplied ; The actual code for the application goes here ; Note that the (de) parameter can never be 0 or 0ffffh on entry. ; If this system is really an i/o driver (such as a foreign disk ; reader/writer) then the RSX itself never need do anything, and ; this code can be left alone (except that a DOS call is available ; to trigger the message below). ; Return any value in hl. rsx: ld hl,info; return info address only ret; Used to reconfigure the driver ; ; This routine MUST be supplied. It may be only a "ret" ; for DSKDRIVE recalculates some parameters in case the drive ; configuration has been changed, and ensures drive reset. ; a,f,b,h,l bthook: xor a ld (hstact),a ld (unacnt),a ld hl,tbuff ld (dmaadr),hl; BIOS view of it ld a,(hstblk) ld b,-1 bthk1: inc b; compute secshf from hstblk rra; cy was clear or a jp nz,bthk1; secshf := log2(hstblk) ld a,b ld (secshf),a ld a,(cpmspt) ld b,a ld a,(config) and 0fh; mask off density selector cp 1 jp nz,bthk4 ld a,b or a rrca ld b,a bthk4: ld a,(secshf) bthk5: or a jp z,bthk6 push af ld a,b rrca ld b,a pop af dec a jp bthk5 bthk6: ld a,b ld (offsec),a ret ; ; ----------- Driver configuration area ------------ ; ; Host specific ports ctrlpt equ 10H; Controller ports (WD 1791) statpt equ ctrlpt trkreg equ ctrlpt+1 secreg equ ctrlpt+2 datapt equ ctrlpt+3 ; ; 1791/2/3 Controller chip specific ; For most Z80 implementations the data request and interrupt from ; the 1791 is connected to NMI (possibly gated by the CPU halt ; status line). Thus a CPU halt is ended by the appropriate ; controller status, with the RET instruction in memory location ; 066h. A status read after the interrupt will reset the interrupt, ; as will satisfying the data request. 8080s need more hardware. wrtmsk equ 0FCH; Status bits useful after write rdmask equ 09CH; " " read clrcmd equ 0CH; clear controller/reset rdcmd equ 08CH; read physical sector wtcmd equ 0ACH; write physical sector seekcmd equ 014H; seek track ; ; Kaypro Specific bitpt equ 01CH; KAYPRO control drvmsk equ 3; bits 0 & 1 select drive sideb equ 4; side selection bit prtbsy equ 8; Parallel printer busy (in) prtstrb equ 16; strobe printer data densb equ 32; density selection bit, 1=single mtroff equ 64; 1 bit stops drive motors bank equ 128; Select memory bank. ; ; *** This area is accessed/altered by support programs *** info: ; The "info" area has been organized to fit into exactly 128 bytes ; for convenience in auxiliary programs that reconfigure this. ; ; The CPM standard Disk Parameter Block, extended ; Modification to this structure changes the driver ; dpblk: ; Different values for: KPRO4 KPRO2 HP125 TVTS802 ; CPM sectors/physical track cpmspt: dw 32; 40 40 32 36 ; log2(sectors/cpmallocation block) bsh: db 4; 4 3 3 4 ; blm, exm calculated from bsh db 0FH,01H; 0Fh,1 7,0 7,0 0Fh,0 ; total blocks on drive dsm: dw 157; 196 194 251 170 ; directory entries - 1 drm: dw 63; 63 63 127 63 ; directory/other blocks pre-allocated db 80H,00H; 0C0h,0 0F0h,0 0F0h,0 080h,0 ; cks=(drm+1)/4 (or 0 for fixed) cks: dw 16; 16 16 32 16 ; reserved tracks off: dw 1; 1 1 3 4 ; CPM extension. 1,2,4,8 sectors/phys record hstblk: db 4; 4 4 2 2 ; ; The sector skew translation table. Room for 10k/track sxltbl: db 0, 1, 2, 3, 4, 5, 6, 7 db 8, 9,10,11,12,13,14,15 db 16,17,18,19,20,21,22,23 db 24,25,26,27,28,29,30,31 db 32,33,34,35,36,37,38,39 db 40,41,42,43,44,45,46,47 db 48,49,50,51,52,53,54,55 db 56,57,58,59,60,61,62,63 db 64,65,66,67,68,69,70,71 db 72,73,74,75,76,77,78,79 ; ; Customization tables for system db 0; SPARE BYTE ; Physical ID of 1st sector on track sec1st: db 1; 0 0 1 1 ; Strategy in computing physical addresses config: db 4; 2 0 2 2 ; (high bit set for single density) ; = 0: 1 sided, phys adr = hst adr ; 1: sec>offsec side1, adrsec=off-sec ; 2: odd trks on side 1, trkadr=hstrk/2 ; 3: side1 is ntrks up ; 4: side1 is ntrks up, count backward maxcase equ 4 ; Number of tracks on physical disk ntrks: db 40; 40 40 40 40 ; Correction to side 1 sector adr. nsecs: db 0; 10 0 0 0 ; ; This portion of "actmsg" is used as ID by altdrive. altid: db 'IBM PC (CP/M-86 DSDD)$$$$$$' ; At least one terminal "$" must be left in above message ; DO NOT CHANGE LENGTH OF MESSAGE infosz equ $-info ; ; The following area is not altered when changing disk types, ; but includes the standard CPM configuration tables. This ; portion must be customized on installation to host only. ; modrv: db 1; Which drive to alter (0 = A) dskode: db 02H; and the drive selection coding dendbl: db 0; base command for double density densgl: db 32; base command for single density invert: db 0; 1 to invert data bits on transfer ; ; The CPM standard dpb. Pointers to above and CPM storage. dpb: dw sxltbl; 0 for no translation dw 0,0,0; *** CPM mungs these, must be in RAM *** dw dirbuf; pointer dw dpblk; pointer dw csv; pointer dw alv; pointer ; ; END of Unalterable (except for values) configuration area ; ; "donemsg" and "actmsg" messages MUST be supplied donemsg: db cr,lf,'(DSKDRIVE terminated)$'; on removal actmsg: drvmsg: db cr,lf,'Drive ' drvid: db 'B: set to $' ; ; The revised foreign disk drivers. ; home: ld bc,0 call setrk ld a,(hstwrt) or a jp nz,home1 ld (hstact),a home1: ret ; seldsk: ld a,c ld (sekdsk),a ld a,(modrv) cp c jp nz,@seldk ld hl,dpb ret ; ; Postpone actual selection of originals until read/write ; to avoid motor turn-ons, pre-seeks etc. until needed setrk: ld h,b ld l,c ld (sektrk),hl ret ; setsec: ld a,c ld (seksec),a jp @setsec setdma: ld h,b ld l,c ld (dmaadr),hl jp @setdma ; sectran: ld a,(sekdsk); Needed because RAMDISK fouls ld hl,modrv; by not knowing it was not cp (hl); selected. jp nz,@sectran ld a,d or e jp z,sectran2; no translation, return alone ex de,hl add hl,bc ld l,(hl) ld h,0 ret sectran2: ld h,b ld l,c ret ; oread: ld hl,(sektrk); do original read routine ld b,h ld c,l call @setrk jp @read ; owrite: push bc; do original write routine ld hl,(sektrk) ld b,h ld c,l call @setrk pop bc jp @write ; ; Digital Researchs (flawed) deblocking algorithm. read: ld a,(modrv) ld b,a ld a,(sekdsk) cp b jp nz,oread xor a ld (unacnt),a ld a,1 ld (readop),a ld (rsflag),a ld a,2; mark "write unallocated" ld (wrtype),a jp rdwrt ; write: ld a,(modrv) ld b,a ld a,(sekdsk) cp b jp nz,owrite xor a ld (readop),a; Not a read ld a,c ld (wrtype),a cp 2; Code for "write unallocated" jp nz,chkuna; else wrt to unalloc, set params ld hl,(bsh); l := bsh, minimum 3 ld a,1 write1: rlca dec l jp nz,write1; Compute block size, in sectors ld (unacnt),a ; ld a,(sekdsk) ; ld (unadsk),a ld hl,(sektrk) ld (unatrk),hl ld a,(seksec) ld (unasec),a ; " " ; Check for write to unallocated sector chkuna: ld a,(unacnt) or a jp z,alloc; No unallocated remain dec a ld (unacnt),a; Reduce for next time ; ld a,(sekdsk) ; ld hl,unadsk ; cp (hl) ; jp nz,alloc; Not same disk ld hl,unatrk call eqtrks jp nz,alloc; Not same track ld a,(seksec) ld hl,unasec cp (hl) jp nz,alloc; seksec <> unasec ; inc (hl); unacnt > 0, disk, track sector ok ld a,(hl) ld hl,cpmspt cp (hl) jp c,inbuff; Not overflow to next track xor a ld (unasec),a ld hl,(unatrk) inc hl ld (unatrk),hl inbuff: xor a ld (rsflag),a; match found, mark unnecessary read jp rdwrt ; ; Not an unallocated record, requires pre-read alloc: xor a ld (unacnt),a inc a ld (rsflag),a ; " " rdwrt: xor a; read or write ld (errflg),a ld a,(secshf); Compute host sector ld b,a inc b ld a,(seksec) rla; Saving Hi bit in carry rdwrt1: rra or a; clear carry dec b jp nz,rdwrt1; sekhst := seksec SHR secshf ld (sekhst),a ld hl,hstact ld a,(hl) ld (hl),01H; Set hstact for next or a jp z,filhst; Was not active ; ld a,(sekdsk) ; ld hl,hstdsk ; cp (hl) ; jp nz,flush ld hl,hsttrk call eqtrks jp nz,flush ld a,(sekhst) ld hl,hstsec cp (hl) jp z,match flush: ld a,(hstwrt); Proper disk but not correct sector or a call nz,wthst; dirty buffer, write it out ; " " filhst: ld a,(sekhst) ld (hstsec),a ld hl,(sektrk) ld (hsttrk),hl ; ld a,(sekdsk); May have to fill the host buffer ; ld (hstdsk),a ld a,(rsflag) or a call nz,rdhst xor a ld (hstwrt),a; Mark buffer clean ; " " ; Copy data to or from buffer, depending on "readop" match: ld a,(hstblk) dec a ld hl,seksec and (hl) ld l,a ld h,00H add hl,hl; *128 add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl add hl,hl ld de,buff add hl,de; point to buffer area ex de,hl ld hl,(dmaadr) ex de,hl ld bc,128 ld a,(readop) or a jp nz,rwmov ld a,01H ld (hstwrt),a; Mark buffer dirty for write ex de,hl; And trade move direction ; " " ; Transfer hl^ to de^ for bc, invert bits on "invert" rwmov: ld a,(invert) or a jp z,rwmov2 rwmov1: ld a,(hl) cpl ld (de),a inc de inc hl dec bc ld a,b or c jp nz,rwmov1 jp qflush ; ; No inversion, move as rapidly as possible rwmov2: ldir; *** Z80 code *** ; " " ; Clear host buffer if this is a directory write qflush: ld a,(wrtype) cp 1; Code for directory write ld a,(errflg) ret nz or a ret nz xor a ld (hstwrt),a; will be clean after write ; " " wthst: call rwtbgn wthst1: ei; during seeks call setup or a jp nz,rwtxit ld a,(hstblk) ld e,wtcmd; write command ld c,datapt; I/o port for data ld hl,buff di rrca jp nc,wthst2; Not 128 byte physical rcd ld b,128 call dcmnde jp w256 wthst2: ld b,0 rrca jp nc,wthst3; Not 256 byte physical rcd call dcmnde jp w256 wthst3: rrca jp nc,wthst4; 1024 byte physical rcd call dcmnde jp w512; 512 byte physical rcd wthst4: call dcmnde ; " " w1024: halt; Else 1024 byte rcd. outi; *** Z80 code jp nz,w1024 w768: halt outi jp nz,w768 w512: halt outi jp nz,w512 w256: halt outi jp nz,w256; May be a W128, on entry bc wthst5: in a,(statpt) rra jp c,wthst5; Await non-busy rla and wrtmsk jp z,rwtxit call clear dec d jp nz,wthst1; retry ld a,0FFH; Signal error jp rwtxit ; ; Set up NMI return, save controller track setting rwtbgn: ld a,(0066H) ld (save66),a ld a,0C9H; "RET" instruction for NMI ld (0066H),a in a,(trkreg) ld (trksav),a ld d,10; retries ret ; eqtrks: ex de,hl ld hl,sektrk ld a,(de) cp (hl) ret nz inc de inc hl ld a,(de) cp (hl) ret ; rdhst: call rwtbgn rdhst1: ei call setup or a jp nz,rwtxit ld a,(hstblk) ld e,rdcmd; read command ld c,datapt; port to read data from ld hl,buff di rrca jp nc,rdhst2; Not 128 bytes per phys. block ld b,128 call dcmnde jp r256 rdhst2: ld b,0; i.e. 256 rrca jp nc,rdhst3; Not 256 bytes per block call dcmnde jp r256 rdhst3: rrca jp nc,rdhst4; 1024 byte physical record call dcmnde jp r512; 512 bytes per block rdhst4: call dcmnde ; " " r1024: halt; else 1024 bytes per block ini; *** Z80 code jp nz,r1024 r768: halt ini jp nz,r768 r512: halt ini jp nz,r512 r256: halt ini jp nz,r256; May be r128, on entry (b) rdhst5: in a,(statpt) rra jp c,rdhst5; Await non-busy status rla and rdmask jp z,rwtxit call clear dec d jp nz,rdhst1; retry ld a,0FFH; Signal error ; " " ; Exit, restoring 66, trkreg, setting errflg on (a) rwtxit: push af ld a,(save66) ld (0066H),a ld a,(trksav) out (trkreg),a pop af ei ld (errflg),a ret ; ; docase (a) on table (hl)^ docase: add a add l ld l,a adc h sub l ld h,a ld a,(hl) inc hl ld h,(hl) ld l,a jp (hl) ; cases: dw case0, case1, case2 dw case3, case4 ; setup: ld hl,(hsttrk) ld (adrtrk),hl ld a,(hstsec) ld (adrsec),a xor a ld (side),a; default use side 0 ld a,(config) and 0fh; remove density selection cp maxcase+1 jp nc,error ld hl,cases; Execute the appropriate code call docase jp seek ; case0: ret ; case1: ld a,(adrsec); config = 1 ld hl,offsec sub (hl) ret c ld hl,nsecs add (hl) ld (adrsec),a ld a,1 ld (side),a ret ; case2: ld hl,(adrtrk) ld a,l and 1 ld (side),a ld a,h rra ld h,a ld a,l rra ld l,a ld (adrtrk),hl ret nc; on side 0 ld hl,nsecs ld a,(adrsec); side 1, correct sector address add (hl) ld (adrsec),a ret ; case3: ld a,(adrtrk) ld hl,ntrks cp (hl); tracks per side ret c sub (hl); On second side ld (adrtrk),a ld a,1 ld (side),a ret ; case4: ld a,(adrtrk) ld hl,ntrks cp (hl) ret c; On first side cpl add (hl) add (hl); Now we count down ld (adrtrk),a ld a,1 ld (side),a ret ; error: ld a,0FFH; config unknown, error ret ; ; Host specific routines here - setup for Kaypro 4 seek: ld a,(dsktrk); If we have switched drives set out (trkreg),a; the controller to match history ld a,(config); Set up for side and density or a ld a,(dendbl) jp p,seek1; Normal double density system ld a,(densgl); else set single density command seek1: ld b,a ld a,(side); Move side selection bit into place. and 1 rla rla or b; Compute drive selection/density code ld b,a in a,(bitpt); Keep bank (80), turn mtr on (40=0) push af; clear DDEN (20), keep lptstrobe (10) and 98H; ignore prtbusy(8), clear side(4), or b; drive code (2, 1) bit weights ld b,a ld a,(dskode) or b or 10H; turn off any lptstrobe bit (Kaypro) out (bitpt),a; selecting drive/side/density pop af and 40H call nz,pause; Motor was off, wait for it ld a,(dsktrk) inc a call z,clear; Never used yet ld a,(adrtrk) ld hl,dsktrk cp (hl) jp z,seek4 ; " " out (datapt),a; changing tracks ld a,seekcmd; seek track call dcmnd halt seek3: in a,(statpt) rra jp c,seek3; await non-busy rla and 98H; Check status jp z,seek4 call clear dec d jp nz,seek; retry allowd ld a,0FFH; Else signal error ret ; seek4: ld a,(adrsec); Successful seek ld hl,sec1st add a,(hl) out (secreg),a; set physical sector ld a,(adrtrk) ld (dsktrk),a xor a ret ; ; Clear disc controller. ; a,f clear: ld a,clrcmd call dcmnd halt clear1: in a,(statpt); Await "not busy" rra jp c,clear1 xor a ld (dsktrk),a ret ; ; command (e) to disk controller ; a,f dcmnde: ld a,e ; " " ; command (a) to disk controller ; f dcmnd: out (ctrlpt),a push bc ld b,20 dcmnd1: dec b jp nz,dcmnd1; pause for controller status pop bc ret ; ; pause (b) * 10 Millisec. Set for 2.5 Mhz clock. ; a,f,b pause: push de pause1: ld de,0686H; <<<< Change for other clocks <<< pause2: dec de ld a,e or d jp nz,pause2 dec b jp nz,pause1 pop de ret ; ; ------------------- Initialized storage -------------------------- active: db true; Non zero if active. allows 1..255 ; ; ================================================================== ; **** Put initialized application storage past this point **** ; ================================================================== ; dsktrk: db 0FFH; mark not selected/used? ; ; ------------------ End initialized storage ----------------------- ; ; ------------------- Uninitialized storage ------------------------ segsiz equ $-@base; Relocation bit table starts here dmadr: ds 2; This value is known to BDOS. It is not ; necessarily the same as the BIOS value ; ; ================================================================== ; **** Put uninitialized application storage past this point **** ; ================================================================== ; dmaadr: ds 2; this is the value known to the BIOS secshf: ds 1; log2(hstblk). For run-time addressing ; sekdsk: ds 1; seek ids, at CPM level sektrk: ds 2 seksec: ds 1 ; save66: ds 1; Save/restore 066h, for NMI operation trksav: ds 1; Save/restore controller track reg. ; ;hstdsk: ds 1; Host ids, at blocked level hsttrk: ds 2 hstsec: ds 1 ; adrtrk: ds 2; Physical trk adr. for controller adrsec: ds 1; and sector offsec: ds 1; Sector offset for 2nd side ; sekhst: ds 1; block/deblock variables hstact: ds 1 hstwrt: ds 1 unacnt: ds 1 ;unadsk: ds 1 unatrk: ds 2 unasec: ds 1 errflg: ds 1 rsflag: ds 1 readop: ds 1 wrtype: ds 1 ; side: ds 1 dirbuf: ds 128 csv: ds 64 alv: ds 64 buff: ds 1024; Can handle 1024 byte host sector ; ; --------------- End application uninitialized storage -------------- ; ds 32; Local stack of at least 16 words ; plus any remainder on page ; align to next page boundary align 256,bdosv lclstk: ;************************************************* ; End of segment to be relocated. pages equ ($-@base)/256; So loader knows memory needs. ; end