RDROYA01@ULKYVX.BITNET (Robert Royar) (07/09/86)
* Below is the code for the background utility I have been writing about. * I would appreciate someone's assmebling this code and integrating it into * her own package to see whether it works for others. The simple move to * background operation, I have been using for three months without bugs. I * posted that earlier. This version, with its stack manipulations is new * and has been tested for a week. I cannot guarantee it. At the end of the * assembly code is the 'C' routine that interfaces to uEmacs, but any routine * to output characters should work. Also, this could be a stand alone program * loaded in high memory. If so add some lines of code to the conin routine to * watch for an attention char that will begin the process, or patch your bios * conin routine to call trap #1, after this is installed, whenever the * attention character is typed. * Known Gotcha's: * 1) If a file is open in one process when you switch to the other which also * opens a file, the original DMA address is lost. On return, your original * file will have an incorrect buffer. Solution: write a getdma routine * to save the foreground DMA pointer and reset it on return. * 2) If you have an open file in the foreground and when you go to background, * you change the logged disk drive, when you return to foreground, the * program cannot find the open file. It should save the current disk value * on entry, and restore it on return. Then it would also have to save the * background logged disk, and reset it when switching back to the background * task. I'm too lazy right now. * 3) This problem occurs frequently on my system with or without this * background stuff and is probably a problem with my system. If you * are logged into one drive (say C:) and load ddt (only ddt) from another * (say B:) with a file to load on the command line (i.e. B:DDT FOO.BAR), ddt * goes to lunch, rather the drive does. However, if you load ddt without * the filename on the command line and use the 'e' command, everthing works * fine. Also, if ddt is on a RAMDRIVE, it will load successfully with the * filename on the command line. * a technique for implementing sys() calls from inside a C program. * 1) call initv() once at start-up. * 2) oldhigh_add = hmem * 3) trap #1 (_ccp routine) * 4) reset trap #3 handler to capture warm boot and connin * 5) on exit from background programs, loop to ccp (background) * 6) on ^C reset tpa, trap #3, and return to suspended program * 7) on <ESC> suspend background task and return to foreground * Comment field conventions * * signifies code that runs in U mode * ** signifies code that runs in S mode * *** signifies S code with interrupts disabled * * *** signifies U code with interrupts disabled ssp: = $5a62 * address of SSP at pload ccpmain:= $234a * cpmlib _main on HSC board (not _ccp) log_dsk:= $5abe * address of log_dsk tbl on HSC system .globl _initv _initv: move.l $8c,$80 * set up trap #0 from trap #3 move.l #_ccp,$84 * set up trap #1 move.w #$0,tpab * request current values bsr settpa move.l high,hmem * save original high memory move.w #62,d0 * set supervisor state to get SSP value trap #2 ** move.l #sstack,a0 ** address for new system stack move.l #ssp,a1 ** default SSP * This routine may not be necessary because in most cases the SSP at * load is the same as the default value. setstk: move.l -(a1),-(a0) ** copy current SSP contents to bg_ssp cmpa.l a7,a1 bhi setstk ** bra if a1 is higher than a7 (never?) move.l a0,_ssp ** default stack pointer for background move.w #0,sr ** back to user state rts * * access to ccp through trap #1 _ccp: movem.l d0-d7/a1-a7,regs1 ** save return environment move.l usp,a0 ** save USP for later return move.l a0,_usp move.l #bios_tr,$8c ** set temporary trap #3 handler move.l #$20000,high ** set temporary high memory move.w #$1,tpab ** temporary set parameter bsr settpa ** use temp. in case of crash cmp.w #$0,inbg ** has a background task been suspended? bne restore ** yes, then return to it ccp: move.l _ssp,a7 ** set default ssp jsr ccpmain ** address of _ccpmain on HSC board bra ccp ** loop forever (until ^C) fix: move.l $80,$8c ** reset the original trap #3 handler move.w #$3,tpab ** reset tpa permanently move.l hmem,high ** back to original value bsr settpa bsr reset ** reset disks movem.l regs1,d0-d7/a1-a7 ** reset old environment move.l _usp,a0 ** replace user stack pointer move.l a0,usp rte * Bios trap handler takes over when in background. Passes through all * commands except conin and warm start. Captures these to enable return * to suspended program. Trapping warm start allows the bdos to reinitialize * everything as usual and return on end to background ccp. This routine * assumes the patch to CPM _initexc has been made so that traps #0 and #1 * are left alone by the system, as should be. This routine is borrowed from * the system bios in that it always disables interrupts. Most background * tasks, except during program execution, operate with interrupts disabled. * Could this be a problem on some systems? .globl _outchar bios_tr: move.w #$2700,sr *** disable interrupts cmp.w #4,d0 *** console out request? beq conout *** put char in buffer cmp.w #3,d0 *** was it a conin request? beq conin *** check for return to editor cmp.w #1,d0 *** was it a warm boot? beq wboot *** return to calling program trap #0 *** no, process the request rte *** return conin: trap #0 *** get the character cmp #3,d0 *** was it a ^C? beq fix *** yes, go back to calling program cmp #$7f,d0 *** delete? beq bsp *** change to backspace cmp #2,d0 *** left arrow? beq bsp *** change to backspace cmp #$1b,d0 *** suspend background task command? beq suspend *** save this environment rte *** send char through bsp: move.w #8,d0 *** put ^H in d0 rte *** return * Each running (suspended) program needs its own stacks to avoid interference. * But the editor must use its own stack to allocate text in the [Command] * buffer. That's the reason for the stack manipulation in this routine. conout: link a6,#-6 *** move d1,-2(a6) *** move.w #$0700,sr * *** set user mode without interrupts move.l a7,ra7u * *** save user (sp) move.l _usp,a7 * *** replace old usp move -2(a6),(a7) * *** put char on stack jsr _outchar * *** goto 'C' code to handle allocation move.l ra7u,a7 * *** replace background USP move.l d0,rd0 * *** save return from _outchar move.w #62,d0 * *** back to superviser state trap #2 *** move.l rd0,d0 *** replace _outchar's returned value cmp #$ff,d0 *** -1 means allocation error beq fix *** if so cleanup and leave unlk a6 *** rte *** wboot: move.w #$0,log_dsk *** address of disk select word moveq.l #$d,d0 *** warm boot request to z80 bsr append *** HSC dependent routines to deal with bsr extract *** I/O server move.w #$1,tpab *** temporarily reset tpa to protect editor jsr settpa *** bra ccp *** remain in background * These two routines should probably save and restore the current DMA address * and current logged disk for each process, then restore them on exit. suspend: *** save all environment variables movem.l d1-a7,bgregs move.l usp,a0 move.l a0,bgusp *** save current USP value move.w #$ffff,inbg *** set switch bra fix *** return to editor restore: *** return to suspended task move.w #$2700,sr *** restore SR to trap #3 state move.l bgusp,a0 *** restore all environment variables move.l a0,usp *** restore background USP movem.l bgregs,d1-a7 clr.w inbg *** set up for next time through bra bsp *** return to suspended program reset: move.w #$25,d0 ** reset disks A-C to R/W move.w #$3,d1 trap #2 rts * Sets tpa limits using values in tpab. Use to set TPA before beginning * a background task. On return from task call the function again to * set the high TPA back to the temporary value. This way a crash during * a background task will not leave the memory permanently truncated. On * exit from background, restore original value stored in hmem. The * calling function stores the TPAB parameter at tpab before calling this * function. The tpab should be 1 to set the memory temporarily or 3 for * permanent setting. settpa: movem.l d0-d7/a0-a6,regs2 * not presently necessary move.w #$3f,d0 move.l #tpab,d1 trap #2 movem.l regs2,d0-d7/a0-a6 rts .even .data inbg: dc.w 0 * 0 = no bg task running * $FFFF = bg task active .even .bss tpab: ds.w 1 * parameter for tpa set low: ds.l 1 * storage for low mem value high: ds.l 1 * storage for high mem value .even hmem: ds.l 1 * permanent highmem rd0: ds.l 1 ra7u: ds.l 1 * storage for register user a7 in conout regs1: ds.l 15 * register storage for _ccp and fix regs2: ds.l 15 * register storage for settpa _usp: ds.l 1 * value of USP for _ccp and fix _ssp: ds.l 1 * pointer to stack location in background bgregs: ds.l 15 * register storage for suspended task two bgusp: ds.l 1 * value of USP for for suspended task .even ds.l 300 * space for stack probably overkill sstack:=* .end ----------------- cut -- here ------------------------------------------------- /* Some sample 'C' code */ /* minimum initiation. This assumes you have patched _initexc in CPM.SYS * to work as DRI claims it does, i.e. leaves traps 0 & 1 alone on warm * boot. */ main() { initv(); } /* 'C' code fragment to call installed program */ /* All functions are part of uEmacs */ case 0x03: /* start a ccp subjob */ if ((f=onlywind(NULL, TRUE)) != TRUE) return(f); if ((f=combuf()) != TRUE)/* open [Command] window */ return(f); if ((f=prevwind(NULL, TRUE)) != TRUE) return(f); setmark(NULL,TRUE); gotoeob(NULL,TRUE); update(); asm("trap #1"); return(prevwind(NULL,TRUE)); /* this routine is only necessary for uEmacs interface; otherwise could * be in the assembly code. */ outchar(c) register int c; { switch(c) { case '\r': return(NULL); case '\n': /* newline */ c = newline(NULL,TRUE); break; case CTRL|'X': /* delete line */ c = mdeleln(NULL,TRUE); break; case '\b': /* back delete */ c = backdel(NULL, TRUE); break; default: /* simple insert */ c = linsert(1, c); } if(c==NULL) c = 0xff; /* failed functions return 0, but conout expects -1 for failure */ else { update(); c=NULL; } return(c); }
HELLER%cs.umass.edu@CSNET-RELAY.ARPA.UUCP (07/10/86)
After reading your background routine code, I have some general questions: 1) there are 3 absolute addresses defined at the start (ssp, ccpmain and log_dsk). Where are these addresses? are they somewhere in the guts of CPM.SYS? On my system I can't use absolute addresses. The Stride 400 series (as well as the older Sage II and IV) use two levels of BIOS - one is CPMBIOS and is linked into CPM.SYS, the other is the Stride BIOS which is separate and comes in two flavors: Single-User and Multi-User. I am running the Multi-User Stride BIOS, setup for three CP/M users (each has its own copy of CPM.SYS core resident, at different places in memory with different TPA boundaries). Thus these addresses are not static. Also on my system CPM.SYS is not absolute, relocatable. The bootstrap program does a relocatable load - I don't have to relocate CPM.SYS if I reconfigure the system (add memory, more/fewer users, different size TPA for each user, run in SU mode, etc.). Also, each user task actually loads the SAME CPM.SYS file in different places with different TPA limits. 2) how are these routines linked? with an application program or with CPM.SYS? Or are they just linked as a module of their own? I'd like to be able to use these background routines with two programs I have now: Mince and UNaXcess (a bbs system originally for UNIX). I'd like to hace Mince do the sort of stuff you are doing with uEmacs (compiling a file with the compiler output going to a buffer). UNaXcess under UNIX has the posiblity of running either a shell or some random UNIX editor (ie vi, emacs, etc.) instead of the dumb built-in editor (much like the usual bbs editor), as well as forking off kermit or lmodem for file upload/download. In the verision I have running, all of the system(), pipe() and exec() calls are disabled. Robert Heller