mac-rom/ProcessMgr/Switch.a

449 lines
16 KiB
Plaintext

;
; File: Switch.a
;
; Contains: Assembler support routines required for process switching.
;
; Written by: Erich Ringewald
;
; Copyright: © 1986-1992 by Apple Computer, Inc., all rights reserved.
;
; Change History (most recent first):
;
; <8> 10/28/92 DTY It turns out that going through the trap dispatcher is really
; expensive, so go back to calling the BlockMove routine directly.
; However, set up D1 with the BlockMoveData trap word so that the
; cache doesnÕt get flushed. Add a conditional compile so that
; _BlockMove is used when building PsychicTV, so the emulator can
; still catch BlockMove.
; <7> 10/27/92 DTY Call BlockMoveData through the dispatcher instead of calling the
; BlockMove routine directly. This gets us two wins: 1) If we
; run under GaryÕs emulator, BlockMove is done natively. 2) On
; 68K machines, the cache wonÕt get flushed because we call
; BlockMoveData.
; <6> 3/22/91 DFH csd,#85216: DonÕt disable interrupts while neutralizing VBL
; tasks, because it leaves interrupts off too long. Instead, set
; the inVBL bit to keep the vertical retrace code from messing
; with the queue.
; <5> 1/14/91 DFH (JDR) Conditionalize out AUX support.
; <3> 12/14/90 DFH SwitchAllRegs now is careful to restore the SR before the SP.
; This is so things go smoothly when restoring the SR transitions
; us between supervisor and user modes.
; <2> 12/5/90 DFH Integrated AUX support.
; <0> x/xx/86 ELR New Today.
;
;--------------------------------------------------------------------
CASE OBJECT
LOAD 'ProcessMgrIncludes.D'
INCLUDE 'data.a'
SEG 'kernel_segment'
;-------------------------------------------------------------------------------
; void InitializeProcess. The first code that a process ever executes.
InitializeProcess PROC EXPORT
IMPORT BeginApplication:CODE
jsr BeginApplication ; initialize TB & OS
rts
ENDPROC ; InitializeProcess
;-------------------------------------------------------------------------------
; void FPInit(void)
; Clear out the initial FPU state for this process
FPInit PROC EXPORT
MC68881 ; using the damn FPU!
moveq.l #0,d0
move.l d0,-(sp) ; push null state frame
frestore (sp)+ ; and use it to reset FPU state
rts
ENDPROC ; FPInit
;-------------------------------------------------------------------------------
;
; SwitchAllRegs. This is the machine level context switch routine. It is preferred
; over SwitchCPURegs when a Floating Point Unit (FPU) is present. pOldSP points to the
; stack where the current application's machine context should be saved. pNewSP points
; the stack where the incoming application's machine context is stored.
; Call:
;
; void SwitchAllRegs(StackPtr pOldSP, StackPtr pNewSP);
;
SwitchAllRegs PROC EXPORT
IMPORT SwitchCPURegs:CODE
SaveRegs REG d0/d1 ; working registers
SaveRegsSize EQU 2*4
pOldSP EQU 4+SaveRegsSize ; first parameter
pNewSP EQU 8+SaveRegsSize ; second parameter
MC68881 ; using the damn FPU!
movem.l SaveRegs,-(sp) ; save working registers
move.l pOldSP(sp),d0 ; address of old save area
move.l pNewSP(sp),d1 ; address of new save area
; save the floating point registers of the outgoing process
fsave -(sp) ; save fpu context
tst.b (sp) ; need user regs?
beq.s FPRegsSaved ; nope
fmovem fp0-fp7,-(sp) ; save data regs
fmovem fpcr/fpsr/fpiar,-(sp) ; and control
st -(sp) ; push 'full' flag
FPRegsSaved
; switch the regular CPU regs (in new process after this call!)
move.l d1,-(sp) ; pass new area for SwitchCPURegs
move.l d0,-(sp) ; pass old area for SwitchCPURegs
jsr SwitchCPURegs ; go context switch
addq #8,sp ; skip these parameters
; restore floating point registers of incoming process
tst.b (sp) ; check for null or not-null
beq.s FPUserRegsOK ; no user areas
addq.l #2,sp ; throw away not-null flag
fmovem (sp)+,fpcr/fpsr/fpiar ; restore part of user FP regs
fmovem (sp)+,fp0-fp7 ; restore remainder
FPUserRegsOK
frestore (sp)+ ; restore normal FP regs
movem.l (sp)+,SaveRegs ; restore working registers
rts
ENDPROC ; SwitchAllRegs
;__________________________________________________________________________________
;
; SwitchCPURegs. This is the machine level context switch routine. It is preferred
; over SwitchAllRegs when a Floating Point Unit (FPU) is *not* present. pOldSP points
; to the stack where the current application's machine context should be saved. pNewSP
; points the stack where the incoming application's machine context is stored.
; Call:
;
; void SwitchCPURegs(StackPtr pOldSP, StackPtr pNewSP);
;
pOldSP EQU 4 ; first parameter
pNewSP EQU 8 ; second parameter
RegsToSwitch REG d2-d7/a2-a6 ; regs that we need to switch
SizeOfRegSwitch EQU (11*4) + 2 ; bytes in RegsToSwitch and sr
SwitchCPURegs PROC EXPORT
; save old state on the old stack (PC is already there, from JSR to this routine)
move.l pOldSP(sp),a0 ; get ptr to PEntry->p_sp from stack
move sr,-(sp) ; save old sr
movem.l RegsToSwitch,-(sp) ; save old regs
cmpa #0,a0 ; have place to save resulting sp?
beq.s noSave ; jump if not
move.l sp,(a0) ; save old sp into PEntry
noSave
; set new stack pointer, then the rest of the registers (PC restored via RTS)
move.l SizeOfRegSwitch+pNewSP(sp),a0 ; get new guy's sp
movem.l (a0)+,RegsToSwitch ; set up his saved regs
move.w (a0)+,sr ; restore his sr
movea.l a0,sp ; restore stack ptr after sr
rts ; and return (to new process)
ENDPROC ; SwitchCPURegs
;__________________________________________________________________________________
; disable. Turn off interrupts except for the NMI. Return the interrupt level that
; we wiped. spl is our buddy.
disable PROC EXPORT
moveq #0,d0
move sr,d0 ; get current priority
lsr #8,d0 ; shift into low bytes
andi #7,d0 ; mask trace/supervisor
ori #$0700,sr ; force to level 7
rts
ENDPROC ; disable
;__________________________________________________________________________________
; spl. Set the CPU interrupt level to the given value. Companion of disable.
spl PROC EXPORT
move sr,d0 ; get current setting
move 6(sp),d1 ; get desired level
lsl #8,d1 ; up into top byte
andi #$F8FF,d0 ; down to level 0
or d1,d0 ; set our level bits
move d0,sr ; this is our new sr
rts
ENDPROC ; spl
;__________________________________________________________________________________
; AtomicVBLSave. Neutralizes VBLs whose routine address is located between lowAddr and
; highAddr. Saves restoration information in specified handle. Does this with
; interrupts off so that we're not fouled up by VBLs that alter the VBL queue.
; Call:
;
; void AtomicVBLSave(Handle saveArea, unsigned long lowAddr, unsigned long highAddr);
;
AtomicVBLSave PROC EXPORT
IMPORT disable, spl, dummyvbl:CODE
IF (&TYPE('HAS_AUX_PROCESSMGR') <> 'UNDEFINED') THEN
IMPORT AUXIsPresent:DATA
ENDIF
SaveRegs REG a2-a3 ; registers we need to save and restore
SaveRegSize EQU 2*4 ; size of registers we save on stack
saveArea EQU 4+SaveRegSize ; first parameter
lowAddr EQU 8+SaveRegSize ; second parameter
highAddr EQU 12+SaveRegSize ; third parameter
movem.l SaveRegs,-(sp) ; save regs we use
bset.b #inVBL, qFlags+VBLQueue ; we own the queue now
movea.l lowAddr(sp),a2 ; get low end of range
movea.l highAddr(sp),a3 ; get high end of range
movea.l qHead+VBLQueue,a1 ; get address of first VBL
move.l sp,d2 ; remember stack pointer
; first -- loop to neutralize VBLs, saving their state on the stack (temporarily)
infoLoop
move.l a1,d0 ; all done?
beq.s saveInfo ; jump if so
move.l vblAddr(a1),d0 ; get routine address
_StripAddress ; strip it!
cmp.l d0,a2 ; is it above the base?
bhi.s nextVBL ; jump if not
cmp.l d0,a3 ; is it below the bound?
IF (&TYPE('HAS_AUX_PROCESSMGR') <> 'UNDEFINED') THEN
bhi.s saveVBL ; jump if so, switch this VBL
; VBLs owned by AUX coffs can also be above PhysMemTop
tst.w AUXIsPresent ; running under AUX?
beq.s nextVBL ; if not, check is not valid
cmp.l PhysMemTop,d0 ; is VBL address above PhysMemTop?
bcs.s nextVBL ; if not, VBL needn't be switched
ELSE
bls.s nextVBL ; if not, VBL needn't be switched
ENDIF
; save info in format of a VBLDesc
saveVBL
move.w vblCount(a1),-(sp) ; save count
move.l vblAddr(a1),-(sp) ; save (unstripped) routine address
move.l a1,-(sp) ; save VBL address
; neutralize this VBL
move #$7FFF,vblCount(a1) ; VERY large count
lea dummyvbl,a0 ; address of dummy VBL
move.l a0,vblAddr(a1) ; route VBL there
nextVBL
movea.l vblink(a1),a1 ; move to next VBL
bra.s infoLoop ; and repeat
; second - copy VBL state info into the handle
saveInfo
bclr.b #inVBL, qFlags+VBLQueue ; free the queue for use
movea.l d2,a1 ; get original stack pointer
movea.l saveArea(a1),a0 ; get the handle
move.l d2,d0 ; original stack pointer
sub.l sp,d0 ; size we'll need
move.l d0,d1 ; save it for block move
_SetHandleSize ; adjust the size
bne.s leaveNow ; jump if it didn't work
move.l d1,d0 ; (count) size we'll need
beq.s leaveNow ; jump if we found nothing
movea.l saveArea(a1),a1 ; get the handle
movea.l (a1),a1 ; (destination) get pointer heap block
movea.l sp,a0 ; (source) get pointer to stacked info
_BlockMoveData ; copy the data
; cleanup up and leave
leaveNow
move.l d2,sp ; restore stack pointer
movem.l (sp)+,SaveRegs ; restore regs we used
rts
ENDPROC ; AtomicVBLSave
; dummyvbl. The routine address we stuff in a VBL descriptor that belongs to
; a switched out process. Just resets the counter. This keeps the VBL around.
dummyvbl PROC EXPORT
move #$7FFF,vblCount(a0) ; reset the swapped out vbl.
rts
ENDPROC ; dummyvbl
;__________________________________________________________________________________
; save_lmemtab. Copy the switchable lomem into a safe area.
save_lmemtab PROC EXPORT
IMPORT (switchTabPtr, blockTrapAddr):DATA
SaveRegs REG a2-a4/d1-d2
SaveRegSize EQU 5*4
movem.l SaveRegs,-(sp) ; save work registers
move.l SaveRegSize+4(sp),a4 ; get pointer to PCB storage
move.l switchTabPtr,a2 ; set up ptr to switch tab
move.l blockTrapAddr,a3
; major loop - save all the ranges of lomem we care about
SaveAllLoop
moveq.l #0,d0 ; because _BlockMove takes a longword
move.w (a2)+,d0 ; get length word
beq.s AllSaved ; 0 terminates table
move.l (a2)+,a0 ; get address longword
IF (&TYPE('SWITCH_MEMBLOCKS') <> 'UNDEFINED') THEN
; support for saving contents of handles or pointers -- not needed!!
; NOTE: What if handle is purged??
bclr #15,d0 ; clear top bit
beq.s SaveRange ; branch if wasn't already set
move.l (a0),a0 ; deref once for ptr
bclr #14,d0 ; clear next bit
beq.s SavePtrBlock ; branch if wasn't already set
move.l (a0),a0 ; deref again for handle
tst.w d0 ; is count known?
bne.s SaveRange ; if so, go do it
_GetHandleSize ; set d0 to switch entire handle
bra.s SaveRange ; and go do transfer
SavePtrBlock
tst.w d0 ; is count known?
bne.s SaveRange ; if so, go do it
_GetPtrSize ; set d0 to switch entire ptr
SaveRange
ENDIF
; have source, count, and destination -- decide whether to call BlockMove or to do it by hand
cmp.w #FastBMSize,d0 ; enough for _BlockMove to be efficient?
bge.s UseBlockMove ; if so, branch
; minor loop -- move the bytes ourselves, since it'll be faster than BlockMove
subq.w #1,d0 ; alias it to zero-based
SaveOneLoop
move.b (a0)+,(a4)+ ; move the byte
dbra d0,SaveOneLoop ; and loop
bra.s SaveAllLoop ; now get next entry
; call _BlockMove, since it's faster for larger chunks
;
; <8> Going through the trap dispatcher is more expensive than I thought. Go back
; to calling the BlockMove routine directly. However, set up D1 so that the caches
; still donÕt get flushed. We still want to call the trap for PsychicTV however, so
; that the emulator can do itÕs native BlockMove.
UseBlockMove
move.l a4,a1 ; a1 = destination
add.l d0,a4 ; update our storage address
if not(PsychicTV) then
move.w #$A22E,d1 ; <8>
jsr (a3)
else
_BlockMove ; <8>
endif
bra.s SaveAllLoop ; now get next entry
; cleanup and leave
AllSaved
movem.l (sp)+,SaveRegs ; restore work registers
rts
ENDPROC ; save_lmemtab
;__________________________________________________________________________________
; restore_lmemtab. Copy the saved switchable lomem back into lomem.
restore_lmemtab PROC EXPORT
IMPORT (switchTabPtr, blockTrapAddr):DATA
SaveRegs REG a2-a4/d1-d2
SaveRegSize EQU 5*4
movem.l SaveRegs,-(sp) ; save work registers
move.l SaveRegSize+4(sp),a4 ; get pointer to PCB storage
move.l switchTabPtr,a2 ; set up dest register
move.l blockTrapAddr,a3
; major loop - restore all the ranges of lomem we care about
RestoreAllLoop
moveq.l #0,d0 ; because _BlockMove takes a longword
move.w (a2)+,d0 ; get length word
beq.s AllRestored ; 0 terminates table
move.l (a2)+,a1 ; get address longword
IF (&TYPE('SWITCH_MEMBLOCKS') <> 'UNDEFINED') THEN
; support for saving contents of handles or pointers -- not needed!!
; NOTE: What if handle is purged??
bclr #15,d0 ; clear top bit
beq.s RestoreRange ; branch if wasn't already set
move.l (a1),a1 ; deref once for ptr
bclr #14,d0 ; clear next bit
beq.s RestorePtrBlock ; branch if wasn't already set
move.l (a1),a1 ; deref again for hdl
tst.w d0 ; is it 0?
bne.s RestoreRange ; if not done
_GetHandleSize
bra.s RestoreRange ; and go do transfer
RestorePtrBlock
tst.w d0 ; is it 0?
bne.s RestoreRange ; if not done
_GetPtrSize
RestoreRange
ENDIF
; have source, count, and destination -- decide whether to call BlockMove or to do it by hand
cmp.w #FastBMSize,d0 ; is it big enough to use _BlockMove?
bge.s UseBlockMove ; if so, branch
; minor loop -- move the bytes ourselves, since it'll be faster than BlockMove
subq.w #1,d0 ; alias it to zero-based
RestoreOneLoop
move.b (a4)+,(a1)+ ; move the byte
dbra d0,RestoreOneLoop ; and loop
bra.s RestoreAllLoop ; now get next entry
; call _BlockMove, since it's faster for larger chunks
;
; <8> Going through the trap dispatcher is more expensive than I thought. Go back
; to calling the BlockMove routine directly. However, set up D1 so that the caches
; still donÕt get flushed. We still want to call the trap for PsychicTV however, so
; that the emulator can do itÕs native BlockMove.
;
UseBlockMove
move.l a4,a0 ; setup source pointer for blockmove
add d0,a4 ; and increment pointer by count
if not(PsychicTV) then
move.w #$A22E,d1 ; <8>
jsr (a3)
else
_BlockMove ; <8>
endif
bra.s RestoreAllLoop ; now get next entry
; cleanup and leave
AllRestored
movem.l (sp)+,SaveRegs ; restore work registers
rts
ENDPROC ; restore_lmemtab
END