Switch Stack.c

/*
**  File:       Switch Stack.c
**
**  Contains:   A simple 68K example of a VBL written in C that runs on a
**              private stack. This won't compile for PowerPC, so don't try it.
**              This is a 68K-only thing.
**
**  Written by: Jim Luther (Based on the VBL code from the Technical Note
**              "TB 35 - MultiFinder Miscellanea".) 
**
**  Copyright:  © 1993-1995 by Apple Computer, Inc., all rights reserved.
**
**  Change History (most recent first):
**
**       <8>     3 Feb 98   Quinn   Expanded comment about holding the private stack.
**       <7>     05/30/95   JML     Added check for VM before using HoldMemory and UnholdMemory.
**       <6>     02/18/95   JML     Added HoldMemory and UnholdMemory calls to keep stack in
**                                  physical memory.
**       <5>     01/27/95   JML     Added .r file for resources
**       <4>     01/27/95   JML     Got rid of the THINK C-isms. Now builds with THINK C, MPW C, and Metrowerks C
**       <3>     11/02/93   JML     Added reentrancy comment to VBL
**       <2>     10/13/93   JML     Minor cleanup
**       <1>     10/08/93   JML     First pass
**
*/
 
#include <Types.h>
#include <Memory.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Windows.h>
#include <Menus.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Events.h>
#include <TextUtils.h>
#include <Retrace.h>
#include <LowMem.h>
#include <Gestalt.h>
 
/*----------------------------------------------------------------------------*/
 
/*
**  Define a struct to keep track of what we need to swap stacks.
**
**  WARNING:    Do not change this structure.  The assembly inlines
**              SwitchtoPrivateStack and RestoreStack depend on the
**              exact order of the fields in this record!
*/
struct StackRec
{
    Ptr     ourStackBottom;     /* saved stack bottom where we can find it */
    Ptr     ourStackTop;        /* saved stack top where we can find it */
    Ptr     savedA7;            /* place where VBL saves current A7 */
    Ptr     savedStkLowPt;      /* place where VBL saves StkLowPt */
    Ptr     savedHiHeapMark;    /* place where VBL saves HiHeapMark */
};
typedef struct StackRec StackRec;
typedef StackRec *StackRecPtr;
 
/*
**  Define a struct to keep track of what we need in the VBL.  Put theVBLTask
**  into the struct first because its address will be passed to our VBL task
**  in A0.
*/
struct VBLRec
{
    VBLTask     theVBLTask;     /* the VBL task itself */
    long        VBLA5;          /* saved CurrentA5 where we can find it */
    StackRec    stackRecord;
};
typedef struct VBLRec VBLRec;
typedef struct VBLRec *VBLRecPtr;
 
/*----------------------------------------------------------------------------*/
 
/*
**  Constants used in sample
*/
 
enum
{
    kInterval       =   6,      /* VBL interval */
    rInfoDialog     =   140,    /* DLOG resource ID */
    rStatTextItem   =   1,      /* item number of counter field in dialog */
    
    kStackSize      =   0x4000  /* 16K */
};
 
/*----------------------------------------------------------------------------*/
 
/*
**  Prototypes
*/
 
pascal void DoVBL (VBLRecPtr recPtr);
void        StartVBL(void);
void        main(void);
 
/*----------------------------------------------------------------------------*/
 
/*
**  A global which will be referenced from our VBL Task and the test program
*/
 
long    gCounter;       /* Counter incremented each time our VBL gets called */
long    gVMOn = false;  /* true if System 7 VM is turned on */
 
/*----------------------------------------------------------------------------*/
 
/*
**  GetVBLRec returns the address of the VBLRec associated with our VBL task.
**  This works because on entry into the VBL task, A0 points to the theVBLTask
**  field in the VBLRec record, which is the first field in the record and that
**  is the address we return.  Note that this method works whether the VBLRec
**  is allocated globally, in the heap (as long as the record is locked in 
**  memory) or if it is allocated on the stack as is the case in this example.
**  In the latter case this is OK as long as the procedure which installed the
**  task does not exit while the task is running.  This trick allows us to get
**  to the saved A5, but it could also be used to get to anything we wanted to
**  store in the record.
*/
extern  VBLRecPtr GetVBLRec(void)
    = 0x2008;   /* MOVE.L   A0,D0 */
 
/*
**  SwitchtoPrivateStack and RestoreStack are assembly language inlines
**  that perform the stack switch using the StackRec passed as a parameter
**  to SwitchtoPrivateStack.
*/
 
#pragma parameter SwitchtoPrivateStack(__A0)
extern  void    SwitchtoPrivateStack(StackRec *sr)
    =   {                           /*                                                  ; A0 = the StackRecPtr parameter */
            0x2178, 0x0BAE, 0x0010, /* MOVE.L   HiHeapMark,StackRec.savedHiHeapMark(A0) ; save HiHeapMark */
            0x2178, 0x0110, 0x000C, /* MOVE.L   StkLowPt,StackRec.savedStkLowPt(A0)     ; save StkLowPt */
            0x214f, 0x0008,         /* MOVE.L   SP,StackRec.savedA7(A0)                 ; save current system stack */
            0x42b8, 0x0110,         /* CLR.L    StkLowPt                                ; disable the stack sniffer */
            0x21D0, 0x0BAE,         /* MOVE.L   StackRec.ourStackBottom(A0),HiHeapMark  ; set HiHeapMark to bottom of our stack */
            0x2e68, 0x0004,         /* MOVEA.L  StackRec.ourStackTop(A0),SP             ; switch stacks */
            0x2f08                  /* MOVE.L   A0,-(SP)                                ; save A0 (*sr) on top of private stack */
        };
 
extern  void    RestoreStack(void)
    =   {
            0x205F,                 /* MOVE.L   (SP)+,A0                                ; restore A0 (*sr) from private stack */
            0x2E68, 0x0008,         /* MOVEA.L  StackRec.savedA7(A0),A7                 ; restore system stack */
            0x21E8, 0x0010, 0x0BAE, /* MOVE.L   StackRec.savedHiHeapMark(A0),HiHeapMark ; restore HiHeapMark */
            0x21E8, 0x000C, 0x0110  /* MOVE.L   StackRec.savedStkLowPt(A0),StkLowPt     ; restore the sniffer */
        };
 
/*----------------------------------------------------------------------------*/
 
/*
**  DoVBL is called only by StartVBL()
**
**  WARNING:    It MUST be declared "pascal" so the function will remove the
**              parameters itself!
*/
pascal  void DoVBL (VBLRecPtr recPtr)
{
    gCounter++;                                 /* Show we can set a global */
    recPtr->theVBLTask.vblCount = kInterval;    /* Set ourselves to run again */
}
 
/*----------------------------------------------------------------------------*/
 
/*
**  This is the actual VBL task code.  It uses GetVBLRec to get our VBL record
**  and properly set up A5.  Having done that, switches to a private stack and
**  then  calls DoVBL to increment a global counter and sets itself to run again.
**  Because of the vagaries of C optimization, it calls a separate routine to
**  actually access global variables.  See "OV 10 - Setting and Restoring A5"
**  for the reasons for this, as well as for a description of SetA5.
**  I can switch to my private stack without checking to make sure I haven't
**  already done so because VBLs don't have to worry about begin reentrant.
*/
void StartVBL ()
{
    long        curA5;
    VBLRecPtr   recPtr;
    
    recPtr = GetVBLRec();           /* First get our record */
    curA5 = SetA5(recPtr->VBLA5);   /* Get the saved A5 */
    /* Now we can access globals */
 
    /* Switch to private stack */
    SwitchtoPrivateStack(&recPtr->stackRecord);
    
    /*  Now we're running on our private stack */
    
    /*
    **  Make sure any functions called at this point use pascal calling conventions
    **  so the functions clean up the stack before returning!
    */
    DoVBL(recPtr);                  /* Call another routine to do actual work */
 
    /* Switch back to original stack */
    RestoreStack();
 
    (void) SetA5(curA5);            /* Restore old A5 */
}
 
/*----------------------------------------------------------------------------*/
 
/*
**  Create a dialog just to demonstrate that the global variable
**  is being updated by the VBL Task.  Before installing the VBL, we store
**  our A5 in the actual VBL Task record, using SetCurrentA5 described in
**  OV 10 - Setting and Restoring A5.  We also store the location of our
**  private stack which the VBL code will run under. We'll run the VBL,
**  showing the counter being incremented, until the mouse button is clicked.
**  Then we remove the VBL Task, close the dialog, and remove the mouse down
**  events to prevent the application from being inadvertently switched
**  by MultiFinder.
*/
void main (void)
{
    VBLRec          theVBLRec;
    DialogPtr       infoDPtr;
    DialogRecord    infoDStorage;
    Str255          numStr = "\pTest";
    OSErr           theErr;
    Handle          theItemHandle;
    short           theItemType;
    Rect            theRect;
    long            lastCount = 0;
    long            response;
 
    InitGraf(&qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(NULL);
    InitCursor();
    MaxApplZone();
    
    /* check for VM */
    if ( Gestalt(gestaltVMAttr, &response) == noErr )
    {
        gVMOn = ((response & (1L << gestaltVMPresent)) != 0);
    }
    
    /* Store the current value of A5 in the VBLA5 field. */
    theVBLRec.VBLA5 = SetCurrentA5 ();
 
    /* Allocate memory for our private stack and store it's location in ourStackBottom */
    theVBLRec.stackRecord.ourStackBottom = NewPtrSys(kStackSize);
    if ( theVBLRec.stackRecord.ourStackBottom != NULL )
    {
        if ( gVMOn )
        {
            /*  Make sure the stack is held in physical memory. Otherwise we could
                end up running interrupt time code on a pageable stack. This would
                be bad, because a page fault allocating stack space would attempt
                to create a exception frame on the same interrupt stack, which would
                cause a double bus fault.  See DTS Technote 1094 for details.
            */
            theErr = HoldMemory(theVBLRec.stackRecord.ourStackBottom, kStackSize);
        }
        else
        {
            theErr = noErr;
        }
        if ( theErr == noErr )
        {
            /* Store location of our stack's top in ourStackTop */
            theVBLRec.stackRecord.ourStackTop =
                (Ptr)((unsigned long)theVBLRec.stackRecord.ourStackBottom + kStackSize);
            
            gCounter = 0;   /* Initialize our global counter */
            
            /* Put up the dialog */
            infoDPtr = GetNewDialog (rInfoDialog, (Ptr) &infoDStorage, (WindowPtr) -1);
            DrawDialog (infoDPtr);
            GetDItem (infoDPtr, rStatTextItem, &theItemType, &theItemHandle, &theRect);
            
            /* Set the address of our routine */
            theVBLRec.theVBLTask.vblAddr = NewVBLProc(StartVBL);
            theVBLRec.theVBLTask.vblCount = kInterval;  /* Frequency of task, in ticks */
            theVBLRec.theVBLTask.qType = vType;         /* qElement is a VBL task */
            theVBLRec.theVBLTask.vblPhase = 0;
            
            /* Now install the VBL task */
            theErr = VInstall((QElemPtr)&theVBLRec.theVBLTask);
            
            /* Display the counter until the mouse button is pushed */
            if ( theErr == noErr )
            {
                do
                {
                    if (gCounter != lastCount)
                    {
                        lastCount = gCounter;
                        NumToString(gCounter, numStr);
                        SetIText(theItemHandle, numStr);
                    }
                } while ( !Button () );
                theErr = VRemove((QElemPtr)&theVBLRec.theVBLTask); /* Remove it when done */
            }
            
            /* Finish up */
            CloseDialog (infoDPtr);     /* Get rid of our dialog */
            FlushEvents (mDownMask, 0); /* Flush all mouse down events */
            
            if ( gVMOn )
            {
                /* Release the hold on our stack */
                theErr = UnholdMemory(theVBLRec.stackRecord.ourStackBottom, kStackSize);
            }
            else
            {
                theErr = noErr;
            }
        }
        
        /* Dispose of the private stack */
        DisposePtr(theVBLRec.stackRecord.ourStackBottom);
    }
}