[mod.computers.68k] Revised source to background functions for CP/M-68k

RDROYA01@ULKYVX.BITNET.UUCP (07/12/86)

* assembly source for background commands in CP/M-68k
*****************************************************************************
* UEASM.S  Assembly language support routines for CP/M-68K MicroEMACS       *
*****************************************************************************
* (c) 1986 Robert Royar
* This file may be used for any purposes as long as this copyright notice is
* retained.  I make no warranties as to this program's fitness for any purpose.
*
* Revisions: 10-July-1986
*
* The errors with DDT and buffer lossage were corrected by storing and
* resetting the DMA and default disks on each switch to/from background.
*
* A CTRL-Y command has been added to "warm boot" the background system
* without switching to the foreground.
*
* Some near branches were re-worked to avoid short address overflow. (This
* code is getting too large.)
*
* Implementation notes:
* To make this work, you will need to patch _initexc in the CPM.SYS so that
* the bdos does not reset traps #0 & #1 on warm boot, or you will need to
* reset these traps each time through the wboot procedure in this source.
* You will need to find a few addresses on your system that I have equated
* below.  To give you an idea, my system begins at $400 and ends at $8c00.
* The TPA continues from there up.  Most systems are located at the top of
* memory.  You can use CPMLIB (a relocated version of it examined in DDT) to
* find offsets for _main (ccpmain in this code), defdma, ssp, and _log_dsk.
* If you are going to patch _initexc to work the way DRI says it should (i.e.
* will not init traps #0 & #1), then you will need to list it or trace through
* it.  Somewhere in there (offset = $4738) is a "moveq.l #$20,d1" change this
* to "moveq.l #$22,d1", and each time the bdos warm boots it will skip the two
* RSX trap vectors on initialization. _initexc is at offset $4718 from the
* beginning of the CPM.REL file.  BTW _initexc is called immediately before
* a call to bios "warm boot."
*
* Synopsis:
* A technique for implementing sys() calls from inside a C program, similar
* to the <ESC>$ command in GNU_VAX_EMACS.
*       1) call initv() once at start of program.
*       2) store highest memory value to use later.
*       3) call through trap #1 (_ccp routine).
*       4) reset trap #3 handler to capture warm boot, connin, conout, and
*          set DMA functions.
*       5) on exit from background programs, loop to ccp (background).
*       6) on ^Y exit from background prog with a BDOS reset and bra to ccp.
*       7) on ^C reset tpa, trap #3, and return to suspended program.
*       8) on <ESC> reset tpa, trap #3, suspend background prog and return to
*          foreground program.
*
*       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
* External references:
*                       append - a bios routine to send bytes to my I/O server,
*                                not likely necessary on most systems.
*                       extract- a bios routine to get bytes from my I/O server,
*                                not likely necessary on most systems.
*                       _outchar-a 'C' routine to negotiate allocation with
*                                MicroEmacs.
*

ssp:            equ     $5a62           * address of SSP at pload
* Use DDT to find this
ccpmain:        equ     $234a           * cpmlib _main on HSC board (not _ccp)
* Offset = ($234a - $400) Absolute address = $BDOS + OFFSET
log_dsk:        equ     $5abe           * address of log_dsk tbl on HSC system
defdma:         equ     $66e4           * bios address of current dma pointer
* Discover this by examining the code for bios function 12.

* Use this funtion for all bios I/O (AUX:, LST:, CON:) to avoid any
* infinite loops when calling the MicroEmacs insert routines while in
* background. Example ttgetc() becomes bios(3); ttputc(c) becomes
* bios(4,(long)c)

        .globl  _bios
_bios:  move.w  4(sp),d0                * argv[0]
        move.l  6(sp),d1                * argv[1]
        move.l  10(sp),d2               * argv[2]
        trap    #0                      * this program knows where the real
        rts                             * bios trap vector is

* Call this as soon as the program begins functioning, before calling
* any CON:, AUX:, or LST: I/O.

        .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
        jsr     settpa
        move.w  #0,inbg         * init background flag
        move.w  #0,f_tim        * init first time flag
        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
        move.w  #$19,d0                 ** get current disk
        trap    #2
        move.w  d0,fodsk                ** foreground logged disk
        move.l  defdma,fodma            ** save current dma pointer
        bsr     popdsk                  ** pops background disk environment
        cmp.w   #$0,inbg                ** has a background task been suspended?
        beq     ccp                     ** not in background, jump around
        jmp     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
        move.w  #$ff,f_tim              ** set f_tim to NO
        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
        cmp.w   #12,d0          *** setdma?
        bne     out             *** branch around
        move.l  d1,bgdma        ***
out:    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
        cmp     #$19,d0         *** ^Y interrupt Cancel reset
        beq     cancel
        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                     ***
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
        bsr     savdsk          *** save current disk setup
        bra     fix             *** return to editor
restore:                        *** return to suspended task
        move.w  #$2700,sr       *** restore SR to trap #3 state
        bsr     popdsk          *** restore disk setup
        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
savdsk: move.w  #$19,d0         *** save current disk value
        trap    #2
        move.w  d0,bgdsk
        rts
cancel: move.w  #0,d0           *** warm boot used to clear between
        trap    #2              *** background functions
        rte
popdsk: cmp.w   #$0,f_tim       *** is this the first time through?
        beq     first_t         *** yes, no environ to pop
        move.w  #$0e,d0         *** restore default drive code
        move.w  bgdsk,d1
        trap    #2
        move.w  #$1a,d0         *** use the same dma as when opened
        move.l  bgdma,d1
        trap    #2
first_t:
        rts
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
        bsr     settpa          ***
        bra     ccp             *** remain in background
* Return disk system and buffers to original state
reset:  bsr     savdsk          ** save current disk state
        move.w  #$1a,d0         ** reset dma
        move.l  fodma,d1
        trap    #2
        move.w  #$0e,d0         ** return disks to original login
        move.w  fodsk,d1
        trap    #2
        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: move.w  #$3f,d0
        move.l  #tpab,d1
        trap    #2
        rts

        .even
        .bss
inbg:   ds.w    1               * 0 = no bg task running
f_tim:  ds.w    1               * 0 = first time through background
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
_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
fodma:  ds.l    1               * dma *address in foreground (Editor)
bgdma:  ds.l    1               * dma *address in background
fodsk:  ds.w    1               * foreground (Editor) logged disk
bgdsk:  ds.w    1               * background logged disk
        .even
        ds.l    300             * space for stack probably overkill
sstack:=*
        .end

------------------------------ cut - here ------------------------------------
/* 'C' code for MicroEmacs interface. Note calling conventions are version
 * 28, not 35 or 36
 */
main(argc,argv)
register int argc;
register char **argv;
{
/* usual declarations */
        initv();        /* init trap handlers #0 & #1, and set-up the
                         * background system.
                         */
}

/* Bound to AGRAVE CTRL-C on my system */

                case 0x03:      /* start a ccp subjob */
                        if ((f=onlywind(NULL, TRUE)) != TRUE)
                                return(f);
                        if ((f=combuf()) != TRUE)/* open [Command] window */
                                return(f);
                        /* combuf() is a new function not in original
                         * source. Depends on a new addline() also
                         */
                        if ((f=prevwind(NULL, TRUE)) != TRUE)
                                return(f);
                        setmark(NULL,TRUE);
                        gotoeob(NULL,TRUE);
                        update();
                        asm("trap #1"); /* was 4 */
                        return(prevwind(NULL,TRUE));

/* Handles the [Command] buffer line allocation */

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;
        else
                {
                update();
                c=NULL;
                }
        return(c);
}