[mod.computers.68k] Source code for the background Routines

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