; 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 end