n3ABSTRACTTXT~ÑBANKSWAPDOCE¿BANKSWAPASM#U¸RSXMAKERSUBx£™ÿÿÿBANKSWAP.LBR 15 December, 1985 Keywords: CPM CPMPLUS RSX DDT SID BANKSWITCHING Abstract: BANKSWAP is an extension for DDT or SID that allows these debugging tools to access code or data in alternate banks of memory on a CPM Plus system. The program is described in an article in Dr. Dobb's Journal for December 1985. ed. For ease of use Š BANKSWAP installs a vector BANKSWAP: A Banked Memory Debugging Tool for CP/M Plus Albert S. Woodhull Hampshire College Amherst, MA 01002 Copyright 1985, Albert S. Woodhull The BANKSWAP.LBR file contains, in addition to this READ.ME file, the source, BANKSWAP.ASM, and RSXMAKER.SUB, which can be used to control the process of assembly, linking, and attaching the resident system extension to an executable file. A complete description of the program was published in Dr. Dobb's Journal, Volume 10, Issue 12, December 1985, on pages 34-52. The following text is abstracted from that article: BANKSWAP is not a standalone program; it is an enhancement to SID or DDT that provides additional commands to copy blocks of memory from bank to bank. The normal functions of the debugger can be used on a copy of memory from another bank that has been brought to the TPA bank. BANKSWAP is relocatable, and is not necessarily loaded to the same location each time it is used. For ease of use BANKSWAP installs a vector to its own entry point at the RST 5 location (28H) during installation. Typing "G28" from the SID or DDT prompt brings up the BANKSWAP menu. The initialization process also displays a message to remind the user of the presence of BANKSWAP and the command to access it. The BANKSWAP menu allows choice of the direction of the move, the memory addresses for the source and destination, and the length of the block to be moved. The menu also provides for easy return to DDT or SID, and for the eventual removal of BANKSWAP. The listing contains comments that explain the operation of BANKSWAP, but I will emphasize a few points I found important in working with banked memory. Although I wrote this program for use on an Apple with the A.L.S. CP/M Card there should be no problems in making BANKSWAP work on other implementations of CP/M Plus. The most critical point is to be sure that control is not lost while bank 1 is deselected. This means ensuring that BANKSWAP itself, the stack, and all data areas used are located in common memory. It is possible for a CP/M Plus system to be constructed so interrupts and system calls can be handled while alternate banks are selected. However, for insurance I thought it best to disable interrupts and avoid calls to the standard BDOS entry point while bank 1 is deselected, since the vectors on page 0 of bank 1 are then inaccessible. There is a potential problem with the version of BANKSWAP shown in listing 1, but I leave it for someone else to fix. As noted above, BANKSWAP uses the RSX technique in order to be located at the highest available meory address. The actual amount of high memory that is common depends upon the hardware used, and I didn't figure out a way for a program to determine this. If too many RSXs are installed, the top of available memory can be below the common region. In this case BANKSWAP will probably cause a crash. On my Rev. A CP/M card the common memory limit is at 8000H, ahich leaves room for a lot of RSXs, so I have never given the problem a high priority. The BANKSWAP.LBR file is being uploaded to the CompuServe CP/M SIG on December 15, 1985. The author can be reached by EMAIL to 74156,1067 on CompuServe, or by mail to the address at the beginning of this file. He would appreciate being informed of further distribution of these files. m with the version of BANKSWAP shown in listing 1, but I leave it for someone els; BANKSWAP.ASM ; ; A. S. Woodhull 28 June 83 ; rev 1 July 85 -- minor editing ; 20 Oct 83 ; ; This is designed to be run as a subprogram under DDT ; or SID, in a banked version of CP/M 3.0 The function ; of BANKSWAP is to copy blocks of memory from other ; banks to and from bank 1, using a buffer in the common ; area. Normal DDT or SID functions can then be ; performed, using the copy of the code in bank 1. ; (Of course, you cannot trace through program segments ; that switch banks or access memory mapped I/O in ; another bank.) ; ; BANKSWAP must reside in the common area of memory, and ; a buffer area through which data can be copied must ; also be present in the common area. BANKSWAP is to be ; assembled as a Resident System Extension (RSX), which ; will automatically be relocated to the top of the TPA. ; It is assumed that the common area is large enough to ; allow an RSX to fit--if this is not true BANKSWAP will ; not work in its present form. ; ; Because the location of BANKSWAP in memory is not fixed ; a jump through a fixed location is set up when the BANK- ; SWAP code is installed. In this version the RST 5 vector ; at 28H is used, but any convenient location on page zero ; may be used. ; ; Under CP/M 3 direct access of BIOS routines is generally ; to be avoided by user programs, since BIOS routines may ; have expectations about which bank is selected when they ; are called. We will, however, do bank switching through ; the BIOS selmem routine. For generality we will use the ; BIOS vector at location 1. ; true: equ 0ffffh false: equ not true ; alone: equ false ;make false if attached to DDT/SID ; biosv: equ 1 ;address of wboot in BIOS found here selmem: equ 4eh ;offset from wboot ; buflen: equ 100h ;move block size ; ; default parameters movcnt: equ 1000h ;to move 16 pages (4K) at a time b0dft: equ 100h ;start of block in bank 0 b1dft: equ 100h ;start of block in bank 1 ; ; zero page addresses bdos: equ 5 ;bdos entry point rstv: equ 28h ;RST 5 used as entry vector ; ; BDOS functions used conin: equ 1 ;get a char printf: equ 9 ;print a string rdbuf: equ 10 ;read a line ; ; This is standard prefix for a Resident System Extension ; see CP/M 3 Programmer's Guide, 1st ed., sec. 4.4, p.168 ; serial: db 0,0,0,0,0,0 start: jmp install ;one-time routine next: jmp 0 ;altered at installation prev: dw 0 ; if alone rmvflg: db 0 ;keep this in memory else rmvflg: db 0ffh ;remove when main program ends endif ; nonbnk: db 0 ;banked system db 'BANKSWAP' loader: db 0 db 0,0 ; *** note: If BANKSWAP is to be attached directly to SID.COM ; or DDT.COM then make rmvflg 0ffh to force removal ; bankexam: lxi h,0 ;get stack pointer dad sp ;...and hold it for return shld holdsp ;...reset SP to a location lxi sp,locstk ;...in common memory ; ; Main loop--exit by Quit or Remove command bankex2: call prompt ;this returns address of sub call doit ;do subroutine addressed by HL jmp bankex2 ; prompt: lxi d,menu ;get ready for menu lda quietflag ora a ;suppress menu? jz prmpt2 prmpt1: lxi d,query ;set for prompt only prmpt2: mvi c,printf call bdos ; Get a character mvi c,conin call bdos call crlf ; make upper case, reject non-alpha ani 5fh cpi 'A' jc prmpt1 ;ask again if invalid cpi 'Z'+1 jnc prmpt1 ;ask again if invalid ; find match in alphtbl lxi b,altblen ;length of table lxi h,alphtbl+altblen ;work back try: cmp m jz match dcx h dcr c ;count down jnz try ; if c= 0 no match found. Now form address match: lxi h,addrtbl dad b ;add offset dad b ;again, 2 bytes per table entry ; get the command address mov a,m inx h mov h,m mov l,a ret ;HL has action address ; doit: pchl ;call here to use action address ; getbank: lhld b0start ;setup addresses shld source lhld b1start shld dest lda length+1 ;low byte ignored sta count ; page by page take a chunk of bank 0 to buffer, then move ; to bank 1 destination get2: di ;be sure no interrupts mvi a,0 lhld selmv call doit call getbuf mvi a,1 lhld selmv ;point to BIOS selmem routine call doit ei ;interrupts safe again call putbuf lxi d,buflen lhld source dad d shld source lhld dest dad d shld dest lxi h,count ;repeat for required # of pages dcr m jnz get2 ret ; putbank: lhld b1start shld source lhld b0start shld dest lda length+1 ;low byte ignored sta count ; page by page, move bank 1 data to buffer, then move it ; to bank 0 put2: call getbuf di ;be sure no interrupts mvi a,0 lhld selmv call doit call putbuf mvi a,1 lhld selmv call doit ei ;interrupts safe again lxi d,buflen lhld source dad d shld source lhld dest dad d shld dest lxi h,count dcr m jnz put2 ret ; source to buffer getbuf: lhld source lxi d,buffer jmp pb1 ; buffer to dest putbuf: lhld dest lxi d,buffer xchg ; common code for getbuf and putbuf pb1: lxi b,buflen ; move (BC) bytes from (HL) to (DE) (could use BIOS move ; routine for this) move: mov a,m stax d inx h inx d dcx b mov a,b ora c jnz move ret ; ; Go back to SID/DDT quit: lxi d,qmsg ;say how to get back mvi c,printf call bdos lhld holdsp ;restore stack pointer sphl rst 7 ;back to DDT or SID ; if alone ; Set for removal on termination of SID or DDT remove: lxi h,rmvflg ;set the remove flag mvi m,0ffh ;...in the RSX prefix lxi h,rstv ;then wipe out the entry mvi m,0ffh ;...JMP with an RST 7 rst 7 ;leave via the debugger endif ;alone ; ; Set up addresses for move, also set up length adset: lxi d,b0id ;tell current bank 0 addr mvi c,printf call bdos lhld b0start ;get the address call addro ;and print it lxi h,b0start call update ;enter hex to (HL) lxi d,b1id ;do it again for bank 1 mvi c,printf call bdos ;tell lhld b1start call addro lxi h,b1start call update ;get new address, if any ; Can fall through from adset or enter directly here to set ; length of block moved lnset: lxi d,lnmsg ;tell current length mvi c,printf call bdos lhld length call addro lxi h,length call update ;offer to change it call crlf ret ; ; Toggle menu off/on xpert: lda quietflag cma ;toggle sta quietflag ret ; ; Get here on invalid command na: lxi d,namsg ;say can't do it mvi c,printf call bdos ret ; crlf: push b push d push h push psw lxi d,crlfstring mvi c,printf call bdos pop psw pop h pop d pop b ret ; ; get string, do nothing if null, else convert, store at (HL) update: push h ;save the location ; loop back here if input is not valid upd1: lxi h,0 ;initial buffer shld inbuf+1 lxi d,chquery ;say what's up mvi c,printf call bdos lxi d,inbuf mvi c,rdbuf ;read console until call bdos call crlf lda inbuf+1 ;get length of hex string ora a ;check for 0 length input jnz convert ; null string, go back pop h ;retrieve value at entry ret ; ; Convert the hex string in the buffer to binary convert: lxi h,0 ;start with a zero mov b,a ;hold length in B lxi d,inbuf+2 conv2: ldax d ;get first (or next) char inx d cpi 60h jc conv3 ani 5fh ;make lower case if necessary conv3: sui '0' jm upd1 ;must be valid hex, 0..9, A..F cpi 0ah jc num ;jump if a good numeric sui 7 cpi 0ah jc upd1 ;error if not good alpha cpi 10h jnc upd1 ;error if not good alpha num: dad h ;multiply current val by 16 dad h dad h dad h add l ;add new least significant digit mov l,a dcr b ;countdown the digits jnz conv2 xchg ;result to DE pop h ;HL at entry says where to it mov m,e inx h mov m,d ret ; ; Print HL as hex addro: push d push h xchg lxi h,hexstr ;where to build string mov a,d call byte ;get A as 2 ASCII chars at (HL) mov a,e call byte ;again, low byte lxi d,hexstr mvi c,printf call bdos ;print it pop h pop d ret ; ; Convert byte to hex ASCII chars, put at (HL) byte: push psw rar ;get high nybble rar rar rar call nybble pop psw ;fall through for low nybble ; nybble makes 1 char, advances output pointer nybble: ani 0fh adi '0' cpi 3ah jc nput adi 7 nput: mov m,a inx h ret ; ; Acceptable command inputs go in this table alphtbl: db 0 ;dummy for no match db 'A' db 'G' db 'L' db 'P' db 'Q' ; if alone db 'R' endif ;alone ; db 'X' altblen: equ $-alphtbl ; addresses of action routines, same order as alphtabl addrtbl: dw na ;not available dw adset ;address set dw getbank dw lnset ;length set dw putbank dw quit ; if alone dw remove endif ; dw xpert ;expert mode, no menu ; menu: db 'Bankswap 1.0 by A. S. Woodhull 10/20/83',0dh,0ah db 'functions available:',0dh,0ah db ' A...set move Addresses',0dh,0ah db ' G...Get alternate bank',0dh,0ah db ' L...set move Length',0dh,0ah db ' P...Put alternate bank',0dh,0ah db ' Q...Quit to SID or DDT',0dh,0ah ; if alone db ' R...Remove BANKSWAP',0dh,0ah endif ; db ' X...eXpert mode (no menu)',0dh,0ah query: db 0dh,0ah,'?? $' namsg: db '...function not available.' crlfstring: db 0dh,0ah,'$' b0id: db 'Bank 0 addr: $' b1id: db 'Bank 1 addr: $' lnmsg: db 'Length is now $' chquery: db 'Change to? (CR to keep): $' hexstr ds 4 db 'H',0dh,0ah,'$' qmsg: db 'Re-enter BANKSWAP from DDT or SID by "G28"' db 0dh,0ah,'$' ; quietflag: db 0 ;initialized off inbuf: db 8 ;max length of buffer ds 9 ; ; default parameters: alter by Set and Length commands b0start: dw b0dft b1start: dw b1dft length: dw movcnt ; ; One-time routine, on 1st BDOS call intercepted install: push b ;keep everything as it was push d ;...so BDOS function can be push h ;...completed push psw ; set up restart vector for re-entry mvi a,0c3h ;a JMP instruction sta rstv lxi h,bankexam shld rstv+1 ; set up address of BIOS routine accessed directly lhld biosv ;find where BIOS is lxi d,selmem ;...and add offset dad d shld selmv ; then patch the RSX prefix to prevent reinstallation lhld next+1 shld start+1 ; tell 'em we're ready mvi c,printf lxi d,imsg call next pop psw ;continue with the task that pop h ;...was so rudely interrupted pop d pop b jmp next ; imsg: db 'BANKSWAP loaded. To access from DDT or ' db 'SID type "G28"' db 0dh,0ah,'$' ; ; reuse installation code area for stack space locstk: ; ; uninitialized storage selmv: ds 2 ;used for BIOS call to selmem source: ds 2 ;for moves to buffer dest: ds 2 ;for moves from buffer count: ds 1 ;blocks to move holdsp: ds 2 ;stack pointer from DDT or SID ; buffer: ds buflen endinstall: push b ;keep everything as it was p; RSXMAKER.SUB assemble RSX and attach to existing COM file ; ASW 21 Oct 83 ; Usage: A>SUBMIT RSXMAKER ; rmac $2 link $2 [op] ren $2.rsx=$2.prl gencom $1 $2  ; imsg: db 'BANKSWAP loaded. To access from DDT or ' db 'SID t