1713 lines
48 KiB
ArmAsm
1713 lines
48 KiB
ArmAsm
.data
|
|
.asciz "@(#)locore.s 1.1 92/07/30"
|
|
.even
|
|
.text
|
|
|syncd to sun3/locore.s 1.70
|
|
|syncd to sun3/locore.s 1.88 (4.1)
|
|
|
|
/*
|
|
* Copyright (c) 1989 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/vmparam.h>
|
|
#include <sys/errno.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <sundev/p4reg.h>
|
|
|
|
#include <machine/asm_linkage.h>
|
|
#include <machine/buserr.h>
|
|
#include <machine/clock.h>
|
|
#include <machine/cpu.h>
|
|
#include <machine/diag.h>
|
|
#include <machine/enable.h>
|
|
#include <machine/interreg.h>
|
|
#include <machine/memerr.h>
|
|
#include <machine/mmu.h>
|
|
#include <machine/pcb.h>
|
|
#include <machine/psl.h>
|
|
#include <machine/pte.h>
|
|
#include <machine/reg.h>
|
|
#include <machine/trap.h>
|
|
#include "fpa.h"
|
|
|
|
#include "assym.s"
|
|
|
|
/*
|
|
* Absolute external symbols
|
|
*/
|
|
.globl _msgbuf, _scb, _DVMA
|
|
/*
|
|
* On the sun3X we put the message buffer and scb on the same page.
|
|
* We set things up so that the first page of KERNELBASE is illegal
|
|
* to act as a redzone during copyin/copyout type operations. One of
|
|
* the reasons the message buffer is allocated in low memory is to
|
|
* prevent being overwritten during booting operations (besides
|
|
* the fact that it is small enough to share a page with others).
|
|
*/
|
|
_msgbuf = KERNELBASE + MMU_PAGESIZE | base addr for message buffer
|
|
_scb = _msgbuf + MSGBUFSIZE | base addr for vector table
|
|
_DVMA = 0xFFF00000 | 32 bit virtual address for system DVMA
|
|
|
|
#if (MSGBUFSIZE + SCBSIZE) > MMU_PAGESIZE
|
|
ERROR - msgbuf and scb larger than a page
|
|
#endif
|
|
|
|
/*
|
|
* The interrupt stack. This must be the first thing in the data
|
|
* segment (other than an sccs string) so that we don't stomp
|
|
* on anything important during interrupt handling. We get a
|
|
* red zone below this stack for free when the kernel text is
|
|
* write protected. Since the kernel is loaded with the "-N"
|
|
* flag, we pad this stack by a page because when the page
|
|
* level protection is done, we will lose part of this interrupt
|
|
* stack. Thus the true interrupt stack will be at least MIN_INTSTACK_SZ
|
|
* bytes and at most MIN_INTSTACK_SZ+MMU_PAGESIZE bytes. The interrupt entry
|
|
* code assumes that the interrupt stack is at a lower address than
|
|
* both eintstack and the kernel stack in the u area.
|
|
*/
|
|
#define MIN_INTSTACK_SZ 0x1800
|
|
.data
|
|
.align 4
|
|
.globl _intstack, eintstack, _ubasic, eexitstack
|
|
_intstack: | bottom of interrupt stack
|
|
. = . + MMU_PAGESIZE + MIN_INTSTACK_SZ
|
|
eintstack: | end (top) of interrupt stack
|
|
. = . + 4 | disambiguate to help debugging
|
|
|
|
_exitstack: | bottom of exit stack
|
|
. = . + MMU_PAGESIZE + MIN_INTSTACK_SZ
|
|
eexitstack: | end (top) of exit stack
|
|
. = . + 4 | disambiguate to help debugging
|
|
|
|
_ubasic: | bottom of struct seguser
|
|
. = . + KERNSTACK
|
|
_ustack: | top of proc 0's stack, start u area
|
|
. = . + USIZE
|
|
intu:
|
|
. = . + USIZE | fake uarea for kernel
|
|
| when no process (as when exiting)
|
|
.globl _uunix | pointer to active u area
|
|
_uunix: .long _ustack | initial uunix value
|
|
|
|
/*
|
|
* Kernel page tables. Must be 16 byte aligned.
|
|
* calculate _kl1pt and _kl2pts at runtime
|
|
*/
|
|
.globl _kptstart
|
|
_kptstart: .=.+(0x10 + KL1PT_SIZE + (KL2PT_SIZE*NKL2PTS))
|
|
|
|
/*
|
|
* System software page tables
|
|
*/
|
|
#define vaddr(x) ((((x)-_Sysmap)/4)*MMU_PAGESIZE + SYSBASE)
|
|
#define SYSMAP(mname, vname, npte) \
|
|
.globl mname; \
|
|
mname: .=.+(4*npte); \
|
|
.globl vname; \
|
|
vname = vaddr(mname);
|
|
SYSMAP(_Sysmap ,_Sysbase ,SYSPTSIZE )
|
|
SYSMAP(_CMAP1 ,_CADDR1 ,1 ) | local tmp
|
|
SYSMAP(_CMAP2 ,_CADDR2 ,1 ) | local tmp
|
|
SYSMAP(_mmap ,_vmmap ,1 )
|
|
SYSMAP(_Mbmap ,_mbutl ,MBPOOLMMUPAGES)
|
|
SYSMAP(_ESysmap ,_Syslimit ,0 ) | must be last
|
|
|
|
.globl _Syssize
|
|
_Syssize = (_ESysmap-_Sysmap)/4
|
|
|
|
/*
|
|
* Old names
|
|
*/
|
|
.globl _Usrptmap, _usrpt
|
|
_Usrptmap = _Sysmap
|
|
_usrpt = _Sysbase
|
|
|
|
/*
|
|
* Software copy of system enable register
|
|
* This is always atomically updated
|
|
*/
|
|
.data
|
|
.globl _enablereg
|
|
_enablereg: .word 0 | UNIX's system enable register
|
|
|
|
/*
|
|
* Scratch location for use by trap handler while aligning the stack
|
|
*/
|
|
.align 4
|
|
trpscr: .long 0
|
|
needsoftint: .long 0
|
|
.text
|
|
|
|
/*
|
|
* Macro to save all registers and switch to kernel context
|
|
* 1: Save and switch context
|
|
* 2: Save all registers
|
|
* 3: Save user stack pointer
|
|
*/
|
|
#define SAVEALL() \
|
|
clrw sp@-;\
|
|
moveml d0-d7/a0-a7,sp@-;\
|
|
movl usp,a0;\
|
|
movl a0,sp@(R_SP)
|
|
|
|
/* Normal trap sequence */
|
|
/*
|
|
* We may need to add a 16 bit pad below the pc here. The pc and sr have already
|
|
* been pushed, so we need to move them down by a word, creating the word
|
|
* pad. This will allow the trap routine to run with the stack longword aligned.
|
|
* By clearing the pad, it will be ignored in trap() and popped off in rei.
|
|
*/
|
|
#define TRAP(type) \
|
|
movl sp,trpscr;\
|
|
andl #3,trpscr;\
|
|
bne 9f;\
|
|
subql #2,sp;\
|
|
movw sp@(2),sp@;\
|
|
movl sp@(4),sp@(2);\
|
|
clrw sp@(6);\
|
|
9: SAVEALL();\
|
|
movl #type,sp@-;\
|
|
jra trap
|
|
|
|
/*
|
|
* System initialization
|
|
* UNIX receives control at the label `_start' which
|
|
* must be at offset zero in this file; this file must
|
|
* be the first thing in the boot image.
|
|
*/
|
|
.text
|
|
.globl _start
|
|
_start:
|
|
|
|
/*
|
|
* We should reset the world here, but it screws the UART settings.
|
|
*
|
|
* Sets up to run out of temporary ptes so we get running at the right
|
|
* addresses.
|
|
*
|
|
* We make the following assumptions about our environment
|
|
* as set up by the monitor:
|
|
*
|
|
* - we have enough memory mapped for the entire kernel + some more
|
|
* - the beginning of dvma space is mapped to real memory
|
|
* - all pages are writable
|
|
* - the monitor's scb is NOT in low memory
|
|
* - on systems w/ ecc/mixed memory, that the monitor has set the base
|
|
* addresses and enabled all the memory cards correctly
|
|
* - MMU is set up in following way:
|
|
* XXX
|
|
*
|
|
* We will set the protections properly in startup().
|
|
*/
|
|
|
|
/*
|
|
* vmunix is linked for running out of high addresses,
|
|
* but gets control running in low addresses. Thus,
|
|
* before we set up the new mapping and start running with the correct
|
|
* addresses, all of the code must be position independent.
|
|
*
|
|
* We continue to run off the stack set up by boot
|
|
* until after we set up the u area (and the kernel stack).
|
|
*/
|
|
movw #SR_HIGH,sr | lock out interrupts
|
|
moveq #FC_MAP,d0
|
|
movc d0,sfc | set default sfc to FC_MAP
|
|
movc d0,dfc | set default dfc to FC_MAP
|
|
movl #0x00008107,sp@- | set up translation window for
|
|
pmove sp@,tt0 | low memory
|
|
addqw #4,sp
|
|
movl #_load_tmpptes,d2 | calculate real location of routine
|
|
subl #KERNELBASE,d2
|
|
movl d2,a2
|
|
jsr a2@ | set up tmp ptes
|
|
movl #rtptr,d2 | calculate real address of variable
|
|
subl #KERNELBASE,d2
|
|
movl d2,a2
|
|
movl d0,a2@(4) | fill in address of table
|
|
pmoveflush a2@,crp | load hardware root ptr
|
|
movl #ICACHE_CLEAR+ICACHE_ENABLE+DCACHE_CLEAR+DCACHE_ENABLE,d0
|
|
movc d0,cacr | clear (and enable) the caches
|
|
jmp cont:l | force non-PC rel branch
|
|
cont:
|
|
|
|
/*
|
|
* PHEW! Now we are running with correct addresses
|
|
* and can use non-position independent code.
|
|
*/
|
|
jsr _early_startup | map stuff in plus other misc
|
|
|
|
/*
|
|
* Set up the stack. From now we continue to use the 68030 ISP
|
|
* (interrupt stack pointer). This is because the MSP (master
|
|
* stack pointer) as implemented by Motorola is too painful to
|
|
* use since we have to play lots of games and add extra tests
|
|
* to set something in the master stack if we running on the
|
|
* interrupt stack and we are about to pop off a throw away
|
|
* stack frame.
|
|
*
|
|
* Thus it is possible to have naming conflicts. In general,
|
|
* when the term "interrupt stack" (no pointer) is used, it
|
|
* is referring to the software implemented interrupt stack.
|
|
* The "kernel stack" is the per user kernel stack in the
|
|
* user area. We handling switching between the two different
|
|
* address ranges upon interrupt entry/exit. We will use ISP
|
|
* and MSP if we are referring to the hardstack stack pointers.
|
|
*/
|
|
|
|
lea _ustack, sp | stack for proc 0
|
|
|
|
/*
|
|
* Disable transparent translation of low memory, since
|
|
* from here on we no longer use the boot-provided stack.
|
|
*/
|
|
movl #0x00000107,sp@- | disable translation window for
|
|
pmove sp@,tt0 | low memory
|
|
addqw #4,sp
|
|
|
|
/*
|
|
* See if we have a 68882 attached.
|
|
* _fppstate is 0 if no fpp,
|
|
* 1 if fpp is present and enabled,
|
|
* and -1 if fpp is present but disabled
|
|
* (not currently used).
|
|
*/
|
|
.data
|
|
.globl _fppstate
|
|
_fppstate: .word 1 | mark as present until we find out otherwise
|
|
fnull: .long 0 | null fpp internal state
|
|
.text
|
|
|
|
flinevec = 0x2c
|
|
movw ENABLEREG,d0 | get the current enable register
|
|
orw #ENA_FPP,d0 | or in FPP enable bit
|
|
movw d0,ENABLEREG | set in the enable register
|
|
movl sp,a1 | save sp in case of fline fault
|
|
movc vbr,a0 | get vbr
|
|
movl a0@(flinevec),d1 | save old f line trap handler
|
|
movl #ffault,a0@(flinevec) | set up f line handler
|
|
frestore fnull
|
|
jra 1f | no fault
|
|
|
|
ffault: | handler for no fpp present
|
|
movw #0,_fppstate | set global to say no fpp
|
|
andw #~ENA_FPP,d0 | clear ENA_FPP enable bit
|
|
movl a1,sp | clean up stack
|
|
|
|
1:
|
|
movl d1,a0@(flinevec) | restore old f line trap handler
|
|
movw d0,ENABLEREG | set up enable reg
|
|
movw d0,_enablereg | save soft copy of enable register
|
|
|
|
| dummy up a stack so process 1 can find saved registers
|
|
lea USRSTACK,a0 | init user stack pointer
|
|
movl a0,usp
|
|
/*
|
|
* We add a 16 bit pad here so the stack is longword aligned for all
|
|
* system calls and traps from user land. It will only get out of
|
|
* alignment on traps that occur after we are already in the kernel. These
|
|
* will be aligned by code in the trap handler.
|
|
* Note that we have to put a nonzero value in the vor part
|
|
* of the word. This is so rei doesn't mistake the dummy stack frame
|
|
* for a stack pad and pop off too many bytes.
|
|
*/
|
|
pea 0x10000 | non-zero pad + dummy fmt & vor
|
|
pea 0 | push dummy pc--filled in by kern_proc
|
|
movw #SR_LOW, sp@- | push sr (for proc made by kern_p
|
|
lea 0,a6 | stack frame link 0 in main
|
|
SAVEALL()
|
|
subl #4, sp@(R_SP) | simulate system call call by pushing
|
|
| syscall # on user stack
|
|
jsr _main | simulate interrupt -> main
|
|
/* NOTREACHED */
|
|
/*
|
|
* If we did reach here, should "pop" syscall #
|
|
* "pushed" on usp.
|
|
*/
|
|
pea 2f
|
|
jsr _panic
|
|
|
|
2: .asciz "setrq"
|
|
.even
|
|
|
|
|
|
/*
|
|
* Entry points for interrupt and trap vectors
|
|
*/
|
|
.globl buserr, addrerr, coprocerr, fmterr, illinst, zerodiv, chkinst
|
|
.globl trapv, privvio, trace, emu1010, emu1111, spurious
|
|
.globl badtrap, brkpt, floaterr, level2, level3, level4, level5
|
|
.globl _level7, errorvec, mmuerr
|
|
|
|
buserr:
|
|
TRAP(T_BUSERR)
|
|
|
|
addrerr:
|
|
TRAP(T_ADDRERR)
|
|
|
|
coprocerr:
|
|
TRAP(T_COPROCERR)
|
|
|
|
fmterr:
|
|
TRAP(T_FMTERR)
|
|
|
|
illinst:
|
|
TRAP(T_ILLINST)
|
|
|
|
zerodiv:
|
|
TRAP(T_ZERODIV)
|
|
|
|
chkinst:
|
|
TRAP(T_CHKINST)
|
|
|
|
trapv:
|
|
TRAP(T_TRAPV)
|
|
|
|
privvio:
|
|
TRAP(T_PRIVVIO)
|
|
|
|
trace:
|
|
TRAP(T_TRACE)
|
|
|
|
emu1010:
|
|
TRAP(T_EMU1010)
|
|
|
|
emu1111:
|
|
TRAP(T_EMU1111)
|
|
|
|
spurious:
|
|
TRAP(T_SPURIOUS)
|
|
|
|
badtrap:
|
|
TRAP(T_M_BADTRAP)
|
|
|
|
brkpt:
|
|
TRAP(T_BRKPT)
|
|
|
|
floaterr:
|
|
TRAP(T_M_FLOATERR)
|
|
|
|
mmuerr:
|
|
TRAP(T_M_MMUERR)
|
|
|
|
errorvec:
|
|
TRAP(T_M_ERRORVEC)
|
|
|
|
level2:
|
|
IOINTR(2)
|
|
|
|
level3:
|
|
IOINTR(3)
|
|
|
|
level4:
|
|
IOINTR(4)
|
|
|
|
.data
|
|
.globl _ledcnt, _ledpat
|
|
_ledpat:
|
|
.byte ~0x80; .byte ~0x40; .byte ~0x20; .byte ~0x10
|
|
.byte ~0x08; .byte ~0x04; .byte ~0x02; .byte ~0x01
|
|
.byte ~0x80; .byte ~0xC0; .byte ~0x60; .byte ~0x30
|
|
.byte ~0x18; .byte ~0x0C; .byte ~0x06; .byte ~0x03
|
|
.byte ~0x01; .byte ~0x80; .byte ~0xC0; .byte ~0xE0
|
|
.byte ~0x70; .byte ~0x38; .byte ~0x1C; .byte ~0x0E
|
|
.byte ~0x07; .byte ~0x03; .byte ~0x01; .byte ~0x80
|
|
.byte ~0xC0; .byte ~0xE0; .byte ~0xF0; .byte ~0x78
|
|
.byte ~0x3C; .byte ~0x1E; .byte ~0x0F; .byte ~0x07
|
|
.byte ~0x03; .byte ~0x01; .byte ~0x80; .byte ~0xC0
|
|
.byte ~0xE0; .byte ~0xF0; .byte ~0xF8; .byte ~0x7C
|
|
.byte ~0x3E; .byte ~0x1F; .byte ~0x0F; .byte ~0x07
|
|
.byte ~0x03; .byte ~0x01; .byte ~0x80; .byte ~0xC0
|
|
.byte ~0xE0; .byte ~0xF0; .byte ~0xF8; .byte ~0xFC
|
|
.byte ~0x7E; .byte ~0x3F; .byte ~0x1F; .byte ~0x0F
|
|
.byte ~0x07; .byte ~0x03; .byte ~0x01; .byte ~0x80
|
|
.byte ~0xC0; .byte ~0xE0; .byte ~0xF0; .byte ~0xF8
|
|
.byte ~0xFC; .byte ~0xFE; .byte ~0x7F; .byte ~0x3F
|
|
.byte ~0x1F; .byte ~0x0F; .byte ~0x07; .byte ~0x03
|
|
.byte ~0x01; .byte ~0x80; .byte ~0xC0; .byte ~0xE0
|
|
.byte ~0xF0; .byte ~0xF8; .byte ~0xFC; .byte ~0xFE
|
|
.byte ~0xFF; .byte ~0x7F; .byte ~0x3F; .byte ~0x1F
|
|
.byte ~0x0F; .byte ~0x07; .byte ~0x03; .byte ~0x01
|
|
.byte ~0xAA; .byte ~0x55; .byte ~0xAA; .byte ~0x55
|
|
endpat:
|
|
.even
|
|
ledptr: .long _ledpat
|
|
flag5: .word 0
|
|
flag7: .word 0
|
|
_ledcnt: .word 10 | 5 per second min LED update rate
|
|
ledcnt: .word 0
|
|
.text
|
|
|
|
/*
|
|
* This code assumes that the real time clock interrupts 100 times
|
|
* a second and that we want to only call hardclock 50 times/sec
|
|
* We update the LEDs with new values so at least a user can tell
|
|
* that something it still running before calling hardclock().
|
|
*/
|
|
.data
|
|
.globl _clk_intr, _clock_type
|
|
_clk_intr: .long 0 | for counting clock interrupts
|
|
.text
|
|
|
|
level5: | default clock interrupt
|
|
tstl _clock_type | check clock type, INTERSIL7170=0
|
|
bne 0f | if not INTERSIL7170, skip
|
|
tstb CLKADDR+CLK_INTRREG | read CLKADDR->clk_intrreg to clear
|
|
0:
|
|
andb #~IR_ENA_CLK5,INTERREG | clear interrupt request
|
|
orb #IR_ENA_CLK5,INTERREG | and re-enable
|
|
tstl _clock_type | check clock type, INTERSIL7170=0
|
|
bne 0f | if not INTERSIL7170, skip
|
|
tstb CLKADDR+CLK_INTRREG | clear interrupt register again,
|
|
| if we lost interrupt we will
|
|
| resync later anyway.
|
|
0:
|
|
| for 100 hz operation, comment out from here ...
|
|
notw flag5 | toggle flag
|
|
jeq 0f | if result zero skip ahead
|
|
rte
|
|
0:
|
|
| ... to here
|
|
moveml d0-d1/a0-a2,sp@- | save regs we trash
|
|
|
|
movl sp,a2 | save copy of previous sp
|
|
cmpl #eintstack,sp | on interrupt stack?
|
|
jls 1f | yes, skip
|
|
lea eintstack,sp | no, switch to interrupt stack
|
|
1:
|
|
|
|
addql #1, _clk_intr | count clock interrupt
|
|
/* check for LED update */
|
|
movl a2@(5*4+2),a1 | get saved pc
|
|
cmpl #idle,a1 | were we idle?
|
|
beq 0f | yes, do LED update
|
|
subqw #1,ledcnt
|
|
bge 2f | if positive skip LED update
|
|
0:
|
|
movw _ledcnt,ledcnt | reset counter
|
|
movb _ledpat,d0
|
|
cmpb #-1,d0
|
|
bne 3f
|
|
movb d0,DIAGREG | d0 to diagnostic LEDs
|
|
bras 2f
|
|
3:
|
|
movl ledptr,a0 | get pointer
|
|
movb a0@+,d0 | get next byte
|
|
cmpl #endpat,a0 | are we at the end?
|
|
bne 1f | if not, skip
|
|
lea _ledpat,a0 | reset pointer
|
|
1:
|
|
movl a0,ledptr | save pointer
|
|
movb d0,DIAGREG | d0 to diagnostic LEDs
|
|
|
|
2: | call hardclock
|
|
movw a2@(5*4),d0 | get saved sr
|
|
movl d0,sp@- | push it as a long
|
|
movl a1,sp@- | push saved pc
|
|
jsr _hardclock | call UNIX routine
|
|
movl a2,sp | restore old sp
|
|
moveml sp@+,d0-d1/a0-a2 | restore all saved regs
|
|
jra rei_io | all done
|
|
|
|
/*
|
|
* Level 7 interrupts can be caused by parity/ECC errors or the
|
|
* clock chip. The clock chip is tied to level 7 interrupts
|
|
* only if we are profiling. Because of the way nmi's work,
|
|
* we clear any level 7 clock interrupts first before
|
|
* checking the memory error register.
|
|
*/
|
|
_level7:
|
|
#ifdef GPROF
|
|
tstl _clock_type | check clock type, INTERSIL7170=0
|
|
bne 0f | if not INTERSIL7170, skip
|
|
tstb CLKADDR+CLK_INTRREG | read CLKADDR->clk_intrreg to clear
|
|
0:
|
|
andb #~IR_ENA_CLK7,INTERREG | clear interrupt request
|
|
orb #IR_ENA_CLK7,INTERREG | and re-enable
|
|
#endif GPROF
|
|
|
|
moveml d0-d1/a0-a1,sp@- | save C scratch regs
|
|
movb MEMREG,d0 | read memory error register
|
|
andb #ER_INTR,d0 | a parity/ECC interrupt pending?
|
|
jeq 0f | if not, jmp
|
|
jsr _memerr | dump memory error info
|
|
/*MAYBE REACHED*/ | if we do return to here, then
|
|
jra 1f | we had a non-fatal memory problem
|
|
0:
|
|
|
|
#ifdef GPROF
|
|
| for 100 hz profiling, comment out from here ...
|
|
notw flag7 | toggle flag
|
|
jne 1f | if result non-zero return
|
|
| ... to here
|
|
jsr kprof | do the profiling
|
|
#else GPROF
|
|
pea 0f | push message printf
|
|
jsr _printf | print the message
|
|
addqw #4,sp | pop argument
|
|
.data
|
|
0: .asciz "stray level 7 interrupt\012"
|
|
.even
|
|
.text
|
|
#endif GPROF
|
|
1:
|
|
moveml sp@+,d0-d1/a0-a1 | restore saved regs
|
|
rte
|
|
|
|
/*
|
|
* Called by trap #2 to do an on chip cache flush operation
|
|
*/
|
|
.globl flush
|
|
flush:
|
|
movl #ICACHE_CLEAR+ICACHE_ENABLE+DCACHE_CLEAR+DCACHE_ENABLE,d0
|
|
movc d0,cacr | clear (and enable) the cache
|
|
rte
|
|
|
|
.globl syscall, trap, rei
|
|
.globl _qrunflag, _queueflag, _queuerun
|
|
#ifdef LWP
|
|
.globl _lwpschedule
|
|
.globl ___Nrunnable
|
|
#endif LWP
|
|
|
|
/*
|
|
* Special case for syscall.
|
|
* Everything in line because this is by far the most
|
|
* common interrupt.
|
|
*/
|
|
syscall:
|
|
subqw #2,sp | empty space
|
|
moveml d0-d7/a0-a7,sp@- | save all regs
|
|
movl usp,a0 | get usp
|
|
movl a0,sp@(R_SP) | save usp
|
|
movl _uunix, a3
|
|
movl #syserr,a3@(U_LOFAULT) | catch a fault if and when
|
|
movl a0@,d0 | get the syscall code
|
|
syscont:
|
|
clrl a3@(U_LOFAULT) | clear lofault
|
|
movl d0,sp@- | push syscall code
|
|
jsr _syscall | go to C routine
|
|
addqw #4,sp | pop arg
|
|
#ifdef LWP
|
|
tstl ___Nrunnable | any runnable threads?
|
|
jeq 0f | no
|
|
jsr _lwpschedule | run threads
|
|
movl _masterprocp, sp@-
|
|
jsr _setmmucntxt
|
|
addqw #4, sp
|
|
0:
|
|
#endif LWP
|
|
movl _uunix, a3 | reload a3 because syscall can trash
|
|
orw #SR_INTPRI,sr | need to test atomically, rte will lower
|
|
tstb _qrunflag | need to run stream queues?
|
|
beq 7f | no
|
|
tstb _queueflag | already running queues?
|
|
bne 7f | yes
|
|
addqb #1,_queueflag | mark that we're running the queues
|
|
jsr _queuerun | run the queues
|
|
clrb _queueflag | done running queues
|
|
7:
|
|
bclr #AST_STEP_BIT-24,a3@(PCB_FLAGS) | need to single step?
|
|
jne 4f
|
|
bclr #AST_SCHED_BIT-24,a3@(PCB_FLAGS) | need to reschedule?
|
|
jeq 3f | no, get out
|
|
4: bset #TRACE_AST_BIT-24,a3@(PCB_FLAGS) | say that we're tracing for AST
|
|
jne 3f | if already doing it, skip
|
|
btst #SR_TRACE_BIT-8,sp@(R_SR) | test trace mode
|
|
jeq 5f | if wasn't set, continue
|
|
bset #TRACE_USER_BIT-24,a3@(PCB_FLAGS) | save fact that trace was set
|
|
5:
|
|
bset #IR_SOFT_INT1_BIT,INTERREG | trigger level 1 intr
|
|
3:
|
|
movl a3@(U_BERR_PC),d0 | saved user PC on bus error
|
|
beq 2f | skip if none
|
|
cmpl sp@(R_PC),d0 | if equal, should restart user code
|
|
bne 2f | not equal, skip the following
|
|
clrl a3@(U_BERR_PC) | clear u.u_berr_pc
|
|
| sp points to d0 of kernel stack
|
|
lea sp@(R_KSTK),a0 | a0 points to bottom of kernel stack
|
|
| (a short beyond <0000, voffset>)
|
|
movl a3@(U_BERR_STACK),a1 | a1 points to saved area
|
|
movl a1@+,d0 | d0 has the size to restore
|
|
subl d0,a0 | a0 points to top of new kernel stack
|
|
| (d0), also (to) in dbra loop below
|
|
| load back upper 5 bits of u.u_pcb.pcb_flags to be consistent w/ saved SR
|
|
movl a3@(PCB_FLAGS),d1 | get current pcb_flags
|
|
andl #NOAST_FLAG,d1 | clear upper 5 bits
|
|
orl a1@+,d1 | oring saved upper 5 bits; a1=+4
|
|
movl d1,a3@(PCB_FLAGS) | set pcb_flags
|
|
|
|
| a1 points to saved d0
|
|
movl a0,sp | sp points to top of new kernel stack
|
|
asrl #1,d0 | d0 gets (short count)
|
|
0: movw a1@+,a0@+ | move an short
|
|
dbra d0,0b | decrement (short count) and loop
|
|
|
|
2:
|
|
movl sp@(R_SP),a0 | restore user SP
|
|
movl a0,usp
|
|
movl #ICACHE_CLEAR+ICACHE_ENABLE+DCACHE_CLEAR+DCACHE_ENABLE,d0
|
|
movc d0,cacr | clear (and enable) the caches
|
|
1:
|
|
moveml sp@,d0-d7/a0-a6 | restore all but SP
|
|
addw #R_SR,sp | pop all saved regs
|
|
rte | and return!
|
|
|
|
syserr:
|
|
movl #-1,d0 | set err code
|
|
jra syscont | back to mainline
|
|
|
|
/*
|
|
* We reset the sfc and dfc to FC_MAP in case we came in from
|
|
* a trap while in the monitor since the monitor uses movs
|
|
* instructions after dorking w/ sfc and dfc during its operation.
|
|
*/
|
|
trap:
|
|
moveq #FC_MAP,d0
|
|
movc d0,sfc
|
|
movc d0,dfc
|
|
jsr _trap | Enter C trap routine
|
|
addqw #4,sp | Pop trap type
|
|
|
|
/*
|
|
* Return from interrupt or trap, check for AST's.
|
|
* d0 contains the size of info to pop (if any)
|
|
*/
|
|
rei:
|
|
movl d0, d2 | save d0 in safe place
|
|
btst #SR_SMODE_BIT-8,sp@(R_SR) | SR_SMODE ?
|
|
bne 1f | skip if system
|
|
#ifdef LWP
|
|
tstl ___Nrunnable | any runnable threads?
|
|
jeq 0f | no
|
|
jsr _lwpschedule | run threads
|
|
movl _masterprocp, sp@-
|
|
jsr _setmmucntxt
|
|
addqw #4, sp
|
|
0:
|
|
#endif LWP
|
|
orw #SR_INTPRI,sr | need to test atomically, rte will lower
|
|
tstb _qrunflag | need to run stream queues?
|
|
beq 7f | no
|
|
tstb _queueflag | already running queues?
|
|
bne 7f | yes
|
|
addqb #1,_queueflag | mark that we're running the queues
|
|
jsr _queuerun | run the queues
|
|
clrb _queueflag | done running queues
|
|
7:
|
|
movl _uunix, a0
|
|
bclr #AST_STEP_BIT-24,a0@(PCB_FLAGS) | need to single step?
|
|
jne 4f
|
|
bclr #AST_SCHED_BIT-24,a0@(PCB_FLAGS) | need to reschedule?
|
|
jeq 3f | no, get out
|
|
4: bset #TRACE_AST_BIT-24,a0@(PCB_FLAGS) | say that we're tracing for AST
|
|
jne 3f | if already doing it, skip
|
|
btst #SR_TRACE_BIT-8,sp@(R_SR) | test trace mode
|
|
jeq 5f | if wasn't set, continue
|
|
bset #TRACE_USER_BIT-24,a0@(PCB_FLAGS) | save fact that trace was set
|
|
5:
|
|
bset #IR_SOFT_INT1_BIT,INTERREG | trigger level 1 intr
|
|
3: movl sp@(R_SP),a0 | restore user SP
|
|
movl a0,usp
|
|
movl #ICACHE_CLEAR+ICACHE_ENABLE+DCACHE_CLEAR+DCACHE_ENABLE,a0
|
|
| ptrace, exec, etc. could overwrite instructions, so clear icache
|
|
movc a0,cacr | clear (and enable) the caches
|
|
1:
|
|
tstl d2 | any cleanup needed?
|
|
beq 2f | no, skip
|
|
movl sp,a0 | get current sp
|
|
addw d2,a0 | pop off d2 bytes of crud
|
|
tstw sp@(R_VOR) | see if there's a stack pad
|
|
bne 3f | if not, skip
|
|
addqw #2,a0 | pop off stack padding
|
|
3:
|
|
clrw a0@(R_VOR) | dummy VOR
|
|
movl sp@(R_PC),a0@(R_PC) | move PC
|
|
movw sp@(R_SR),a0@(R_SR) | move SR
|
|
movl a0,sp@(R_SP) | stash new sp value
|
|
moveml sp@,d0-d7/a0-a7 | restore all including SP
|
|
addw #R_SR,sp | pop all saved regs
|
|
rte | and return!
|
|
2:
|
|
tstw sp@(R_VOR) | see if there's a stack pad
|
|
bne 4f | if not, skip
|
|
movl sp@(R_PC),sp@(R_PC+2) | put pc & sr back in real place
|
|
movw sp@(R_SR),sp@(R_SR+2)
|
|
moveml sp@,d0-d7/a0-a6 | restore all but SP
|
|
addw #(R_SR+2),sp | pop all saved regs + stack pad
|
|
rte | and return!
|
|
4:
|
|
moveml sp@,d0-d7/a0-a6 | restore all but SP
|
|
addw #R_SR,sp | pop all saved regs
|
|
rte | and return!
|
|
|
|
/*
|
|
* Return from I/O interrupt, check for AST's.
|
|
*/
|
|
.globl rei_io
|
|
rei_iop:
|
|
moveml sp@+,d0-d2/a0-a2 | pop regs we saved in level5
|
|
rei_io:
|
|
addql #1,_cnt+V_INTR | increment io interrupt count
|
|
rei_si:
|
|
moveml d0-d1/a0-a1,sp@- | save C scratch regs
|
|
btst #SR_SMODE_BIT-8,sp@(16) | SR_SMODE? (SR is atop stack)
|
|
jne 3f | skip if system
|
|
orw #SR_INTPRI,sr | need to test atomically, rte will lower
|
|
tstb _qrunflag | need to run stream queues?
|
|
beq 7f | no
|
|
tstb _queueflag | already running queues?
|
|
bne 7f | yes
|
|
addqb #1,_queueflag | mark that we're running the queues
|
|
jsr _queuerun | run the queues
|
|
clrb _queueflag | done running queues
|
|
orw #SR_INTPRI,sr | need to test atomically, rte will lower
|
|
7:
|
|
movl _uunix, a0
|
|
bclr #AST_STEP_BIT-24,a0@(PCB_FLAGS) | need to single step?
|
|
jne 4f
|
|
bclr #AST_SCHED_BIT-24,a0@(PCB_FLAGS) | need to reschedule?
|
|
jeq 3f | no, get out
|
|
4: bset #TRACE_AST_BIT-24,a0@(PCB_FLAGS) | say that we're tracing for AST
|
|
jne 3f | if already doing it, skip
|
|
bset #SR_TRACE_BIT-8,sp@(16) | set trace mode in SR atop stack
|
|
jeq 5f | if wasn't set, continue
|
|
bset #TRACE_USER_BIT-24,a0@(PCB_FLAGS) | save fact that trace was set
|
|
5:
|
|
bset #IR_SOFT_INT1_BIT,INTERREG | trigger level 1 intr
|
|
3:
|
|
moveml sp@+,d0-d1/a0-a1 | restore C scratch regs
|
|
rte | and return!
|
|
|
|
/*
|
|
* Handle software interrupts that come in at level 1. If it wasn't a
|
|
* softint, then check any devices using level 1 autovector.
|
|
* On the 68030, bad things happen if we set the trace bit (in rei, rei_si,
|
|
* and syscall) in the trap frame before rte as was done on the sun-2/3.
|
|
* Therefore we cause a software interrupt instead, and decide based on
|
|
* the flags and the needsoftint flag whether we need to run the C softint
|
|
* code or reschedule (or both).
|
|
*/
|
|
.globl level1
|
|
|
|
level1:
|
|
bclr #IR_SOFT_INT1_BIT,INTERREG| clear interrupt request
|
|
movl d0,sp@- | save d0
|
|
jbsr _spl7 | disable interrupts to avoid race
|
|
tstl needsoftint | a real software interrupt?
|
|
jeq 2f | no soft interrupt
|
|
clrl needsoftint | clear soft interrupt flag
|
|
movl d0,sp@- | save d0
|
|
jbsr _splx | restore priority
|
|
addql #4,sp | pop splx arg
|
|
movl sp@+,d0 | restore d0
|
|
moveml d0-d2/a0-a2,sp@- | save regs we trash
|
|
movl sp,d2 | save copy of previous sp
|
|
cmpl #eintstack,sp | on interrupt stack?
|
|
jls 0f | yes, skip
|
|
lea eintstack,sp | no, switch to interrupt stack
|
|
0:
|
|
jsr _softint | Call C
|
|
tstl d0 | was there and soft interrupt
|
|
beqs 1f | no, look for devices
|
|
movl d2,sp | restore old sp
|
|
jra 3f
|
|
2:
|
|
movl d0,sp@- |
|
|
jbsr _splx | restore priority
|
|
addql #4,sp | pop splx arg
|
|
movl sp@+,d0
|
|
moveml d0-d2/a0-a2,sp@- /* save regs we trash <d0,d1,d2,a0,a1,a2> */
|
|
movl sp,d2 /* save copy of previous sp */
|
|
cmpl #eintstack,sp /* on interrupt stack? */
|
|
jls 1f /* yes, skip */
|
|
lea eintstack,sp /* no, switch to interrupt stack */
|
|
1: movl #_level1_vector,a2 /* get vector ptr */
|
|
4: movl a2@+,a1 /* get routine address */
|
|
jsr a1@ /* go there */
|
|
tstl d0 /* success? */
|
|
beqs 4b /* no, try next one */
|
|
/* get location of per-device interrupt counter */
|
|
subl #_level1_vector + 4, a2
|
|
addl #_level1_intcnt, a2
|
|
addql #1, a2@ /* count the per-device interrupt */
|
|
movl d2,sp /* restore stack pointer */
|
|
cmpl #0x80000000,d0 /* spurious? */
|
|
jeq 3f /* yes */
|
|
clrl _level1_spurious /* no, clr spurious cnt */
|
|
jra rei_iop
|
|
3:
|
|
movl _uunix, a1
|
|
btst #TRACE_AST_BIT-24,a1@(PCB_FLAGS) | was this an AST?
|
|
moveml sp@+,d0-d2/a0-a2 | restore saved regs
|
|
jeq rei_si
|
|
jra trace
|
|
|
|
/*
|
|
* Turn on a software interrupt (H/W level 1).
|
|
*/
|
|
ENTRY(siron)
|
|
movl #1,needsoftint | this is a real software int
|
|
bset #IR_SOFT_INT1_BIT,INTERREG | trigger level 1 intr
|
|
rts
|
|
|
|
/*
|
|
* return 1 if an interrupt is being serviced (on interrupt stack),
|
|
* otherwise return 0.
|
|
*/
|
|
ENTRY(servicing_interrupt)
|
|
clrl d0 | assume false
|
|
cmpl #eintstack,sp | on interrupt stack?
|
|
bhi 1f | no, skip
|
|
movl #1,d0 | return true
|
|
1:
|
|
rts
|
|
|
|
/*
|
|
* Enable and disable DVMA.
|
|
*/
|
|
ENTRY(enable_dvma)
|
|
1:
|
|
orw #ENA_SDVMA,_enablereg | enable System DVMA
|
|
movw _enablereg,d0 | get it in a register
|
|
movw d0,ENABLEREG | put enable register back
|
|
cmpw _enablereg,d0 | see if someone higher changed it
|
|
bne 1b | if so, try again
|
|
rts
|
|
|
|
ENTRY(disable_dvma)
|
|
1:
|
|
andw #~ENA_SDVMA,_enablereg | disable System DVMA
|
|
movw _enablereg,d0 | get it in a register
|
|
movw d0,ENABLEREG | put enable register back
|
|
cmpw _enablereg,d0 | see if someone higher changed it
|
|
bne 1b | if so, try again
|
|
rts
|
|
|
|
/*
|
|
* Transfer data to and from user space -
|
|
* Note that these routines can cause faults
|
|
* It is assumed that the kernel has nothing at
|
|
* less than USRSTACK in the virtual address space.
|
|
*/
|
|
|
|
| Fetch user byte _fubyte(address)
|
|
ENTRY2(fubyte,fuibyte)
|
|
movl _uunix, a1
|
|
movl #fsuerr,a1@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@(4),a0 | get address
|
|
cmpl #USRSTACK-1,a0 | check address range
|
|
jhi fsuerr | jmp if greater than
|
|
movb a0@,d0 | get the byte
|
|
andl #0xFF,d0
|
|
clrl a1@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
|
|
| Fetch user (short) word: _fusword(address)
|
|
ENTRY(fusword)
|
|
movl _uunix, a1
|
|
movl #fsuerr,a1@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@(4),a0 | get address
|
|
cmpl #USRSTACK-2,a0 | check address range
|
|
jhi fsuerr | jmp if greater than
|
|
movw a0@,d0 | get the word
|
|
andl #0xFFFF,d0
|
|
clrl a1@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
| Fetch user (long) word: _fuword(address)
|
|
ENTRY2(fuword,fuiword)
|
|
movl _uunix, a1
|
|
movl #fsuerr,a1@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@(4),a0 | get address
|
|
cmpl #USRSTACK-4,a0 | check address range
|
|
jhi fsuerr | jmp if greater than
|
|
movl a0@,d0 | get the long word
|
|
clrl a1@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
| Set user byte: _subyte(address, value)
|
|
ENTRY2(subyte,suibyte)
|
|
movl _uunix, a1
|
|
movl #fsuerr,a1@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@(4),a0 | get address
|
|
cmpl #USRSTACK-1,a0 | check address range
|
|
jhi fsuerr | jmp if greater than
|
|
movb sp@(8+3),a0@ | set the byte
|
|
clrl d0 | indicate success
|
|
clrl a1@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
| Set user short word: _susword(address, value)
|
|
ENTRY(susword)
|
|
movl _uunix, a1
|
|
movl #fsuerr,a1@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@(4),a0 | get address
|
|
cmpl #USRSTACK-2,a0 | check address range
|
|
jhi fsuerr | jmp if greater than
|
|
movw sp@(8+2),a0@ | set the word
|
|
clrl d0 | indicate success
|
|
clrl a1@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
| Set user (long) word: _suword(address, value)
|
|
ENTRY2(suword,suiword)
|
|
movl _uunix, a1
|
|
movl #fsuerr,a1@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@(4),a0 | get address
|
|
cmpl #USRSTACK-4,a0 | check address range
|
|
jhi fsuerr | jmp if greater than
|
|
movl sp@(8+0),a0@ | set the long word
|
|
clrl d0 | indicate success
|
|
clrl a1@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
fsuerr:
|
|
movl #-1,d0 | return error
|
|
movl _uunix, a0
|
|
clrl a0@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
/*
|
|
* Copy a null terminated string from the user address space into
|
|
* the kernel address space.
|
|
*
|
|
* copyinstr(udaddr, kaddr, maxlength, lencopied)
|
|
* caddr_t udaddr, kaddr;
|
|
* u_int maxlength, *lencopied;
|
|
*/
|
|
ENTRY(copyinstr)
|
|
movl sp@(4),a0 | a0 = udaddr
|
|
cmpl #USRSTACK,a0
|
|
jcc copystrfault | jmp if udaddr >= USRSTACK
|
|
movl sp@(8),a1 | a1 = kaddr
|
|
movl sp@(12),d0 | d0 = maxlength
|
|
jlt copystrfault | if negative size fault
|
|
movl a2, sp@-
|
|
movl _uunix, a2
|
|
movl #copystrfault,a2@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@+, a2
|
|
jra 1f | enter loop at bottom
|
|
0:
|
|
movb a0@+,a1@+ | move a byte and set CC's
|
|
jeq copystrok | if '\0' done
|
|
1:
|
|
dbra d0,0b | decrement and loop
|
|
|
|
copystrout:
|
|
movl #ENAMETOOLONG,d0 | ran out of space
|
|
jra copystrexit
|
|
|
|
copystrfault:
|
|
movl #EFAULT,d0 | memory fault or bad size
|
|
jra copystrexit
|
|
|
|
copystrok:
|
|
moveq #0,d0 | all ok
|
|
|
|
/*
|
|
* At this point we have:
|
|
* d0 = return value
|
|
* a0 = final address of `from' string
|
|
* sp@(4) = starting address of `from' string
|
|
* sp@(16) = lencopied pointer (NULL if nothing to be return here)
|
|
*/
|
|
copystrexit:
|
|
movl a2, sp@-
|
|
movl _uunix, a2
|
|
clrl a2@(U_LOFAULT) | clear lofault
|
|
movl sp@+, a2
|
|
movl sp@(16),d1 | d1 = lencopied
|
|
jeq 2f | skip if lencopied == NULL
|
|
subl sp@(4),a0 | compute nbytes moved
|
|
movl d1,a1
|
|
movl a0,a1@ | store into *lencopied
|
|
2:
|
|
rts
|
|
|
|
/*
|
|
* Copy a null terminated string from the kernel
|
|
* address space to the user address space.
|
|
*
|
|
* copyoutstr(kaddr, udaddr, maxlength, lencopied)
|
|
* caddr_t kaddr, udaddr;
|
|
* u_int maxlength, *lencopied;
|
|
*/
|
|
ENTRY(copyoutstr)
|
|
movl sp@(4),a0 | a0 = kaddr
|
|
movl sp@(8),a1 | a1 = udaddr
|
|
cmpl #USRSTACK,a1
|
|
jcc copystrfault | jmp if udaddr >= USRSTACK
|
|
movl sp@(12),d0 | d0 = maxlength
|
|
jlt copystrfault | if negative size fault
|
|
movl a2, sp@-
|
|
movl _uunix, a2
|
|
movl #copystrfault,a2@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@+, a2
|
|
jra 1f | enter loop at bottom
|
|
0:
|
|
movb a0@+,a1@+ | move a byte and set CCs
|
|
jeq copystrok | if '\0' done
|
|
1:
|
|
dbra d0,0b | decrement and loop
|
|
jra copystrout | ran out of space
|
|
|
|
/*
|
|
* Copy a null terminated string from one point to another in
|
|
* the kernel address space.
|
|
*
|
|
* copystr(kfaddr, kdaddr, maxlength, lencopied)
|
|
* caddr_t kfaddr, kdaddr;
|
|
* u_int maxlength, *lencopied;
|
|
*/
|
|
ENTRY(copystr)
|
|
movl sp@(4),a0 | a0 = kfaddr
|
|
movl sp@(8),a1 | a1 = kdaddr
|
|
movl sp@(12),d0 | d0 = maxlength
|
|
jlt copystrfault | if negative size fault
|
|
jra 1f | enter loop at bottom
|
|
0:
|
|
movb a0@+,a1@+ | move a byte and set CCs
|
|
jeq copystrok | if '\0' done
|
|
1:
|
|
dbra d0,0b | decrement and loop
|
|
jra copystrout | ran out of space
|
|
|
|
/*
|
|
* copyout(kaddr, udaddr, n)
|
|
* caddr_t kaddr, udaddr;
|
|
* u_int n;
|
|
*
|
|
* We let kcopy do most of the work after verifying that udaddr is
|
|
* really a user address so that we can possibly use the bcopy hardware.
|
|
*/
|
|
ENTRY(copyout)
|
|
cmpl #USRSTACK,sp@(8) | check starting address for udaddr
|
|
jcc cpctxerr | jmp to cpctxerr on err (>= unsigned)
|
|
jmp _kcopy | let kcopy do the rest
|
|
|
|
/*
|
|
* copyin(udaddr, kaddr, n)
|
|
* caddr_t udaddr, kaddr;
|
|
* u_int n;
|
|
*
|
|
* We let kcopy do most of the work after verifying that udaddr is
|
|
* really a user address so that we can possibly use the bcopy hardware.
|
|
*/
|
|
ENTRY(copyin)
|
|
cmpl #USRSTACK,sp@(4) | check starting address for udaddr
|
|
jcc cpctxerr | jmp to cpctxerr on err (>= unsigned)
|
|
jmp _kcopy | let kcopy do the rest
|
|
|
|
cpctxerr:
|
|
movl #EFAULT,d0 | return error
|
|
movl _uunix, a0
|
|
clrl a0@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
/*
|
|
* fetch user longwords -- used by syscall -- faster than copyin
|
|
* Doesn't worry about alignment of transfer, let the 68020 worry
|
|
* about that - we won't be doing more than 8 long words anyways.
|
|
* fulwds(uadd, sadd, nlwds)
|
|
*/
|
|
ENTRY(fulwds)
|
|
movl _uunix, a0
|
|
movl #cpctxerr,a0@(U_LOFAULT) | catch a fault if and when
|
|
movl sp@(4),a0 | user address
|
|
movl sp@(8),a1 | system address
|
|
movl sp@(12),d0 | number of words
|
|
cmpl #USRSTACK,a0 | check starting address
|
|
jcs 1f | enter loop at bottom if < unsigned
|
|
jra cpctxerr | error
|
|
0: movl a0@+,a1@+ | get longword
|
|
1: dbra d0,0b | loop on count
|
|
clrl d0 | indicate success
|
|
movl _uunix, a0
|
|
clrl a0@(U_LOFAULT) | clear lofault
|
|
rts
|
|
|
|
/*
|
|
* Get/Set vector base register
|
|
*/
|
|
ENTRY(getvbr)
|
|
movc vbr,d0
|
|
rts
|
|
|
|
ENTRY(setvbr)
|
|
movl sp@(4),d0
|
|
movc d0,vbr
|
|
rts
|
|
|
|
/*
|
|
* Enter the monitor -- called for console abort
|
|
*/
|
|
ENTRY(montrap)
|
|
jsr _start_mon_clock | enable monitor polling interrupt
|
|
movl sp@(4),a0 | address to trap to
|
|
clrw sp@- | dummy VOR
|
|
pea 0f | return address
|
|
movw sr,sp@- | current sr
|
|
jra a0@ | trap to monitor
|
|
0:
|
|
jsr _stop_mon_clock | disable monitor polling interrupt
|
|
rts
|
|
|
|
/*
|
|
* Trap to halt unix and break to monitor
|
|
*/
|
|
|
|
ENTRY(_halt)
|
|
trap #14
|
|
rts
|
|
|
|
/*
|
|
* Read the ID prom. This is mapped from ID_PROM for IDPROMSIZE
|
|
* bytes in the FC_MAP address space for byte access only. Assumes
|
|
* that the sfc has already been set to FC_MAP.
|
|
*/
|
|
ENTRY(getidprom)
|
|
movl sp@(4),a0 | address to copy bytes to
|
|
lea ID_PROM,a1 | select id prom
|
|
movl #(IDPROMSIZE-1),d1 | byte loop counter
|
|
0: movb a1@+,a0@+ | get a byte
|
|
dbra d1,0b | and loop
|
|
rts
|
|
|
|
/*
|
|
* Read the id information from the eeprom.
|
|
*/
|
|
ENTRY(getideeprom)
|
|
movl sp@(4),a0 | address to copy bytes to
|
|
lea ID_EEPROM,a1 | select id part of eeprom
|
|
movl #(IDPROMSIZE-1),d1 | byte loop counter
|
|
0: movb a1@+,a0@+ | get a byte
|
|
dbra d1,0b | and loop
|
|
rts
|
|
|
|
/*
|
|
* Enable and disable video.
|
|
*/
|
|
ENTRY(setvideoenable)
|
|
1:
|
|
tstl sp@(4) | is bit on or off
|
|
jeq 2f
|
|
orw #ENA_VIDEO,_enablereg | enable video
|
|
jra 3f
|
|
2:
|
|
andw #~ENA_VIDEO,_enablereg | disable video
|
|
3:
|
|
movw _enablereg,d0 | get it in a register
|
|
movw d0,ENABLEREG | put enable register back
|
|
cmpw _enablereg,d0 | see if someone higher changed it
|
|
bne 1b | if so, try again
|
|
rts
|
|
|
|
/*
|
|
* Enable and disable video interrupt.
|
|
* Do not set P4_REG_RESET or cgeight will go black forever!
|
|
*/
|
|
ENTRY(setintrenable)
|
|
tstl sp@(4) | is bit on or off
|
|
jeq 1f
|
|
orb #IR_ENA_VID4,INTERREG
|
|
rts
|
|
1:
|
|
jbsr _spl4 | disable this level to avoid race
|
|
andb #~IR_ENA_VID4,INTERREG | disable video interrupt
|
|
movl d0, sp@- | push spl4 result for splx arg.
|
|
jbsr _splx | restore priority
|
|
addql #4, sp | pop splx arg
|
|
rts
|
|
|
|
#ifdef FPU
|
|
|
|
/*
|
|
* Set the fpp registers to the u area values
|
|
*/
|
|
ENTRY(setfppregs)
|
|
tstw _fppstate | is fpp present and enabled?
|
|
jle 1f | branch if not
|
|
movl _uunix, a0
|
|
fmovem a0@(U_FPS_REGS),fp0-fp7 | set fp data registers
|
|
fmovem a0@(U_FPS_CTRL),fpc/fps/fpi | set control registers
|
|
1:
|
|
rts
|
|
|
|
/*
|
|
* Save the internal FPP state at the address specified in the argument.
|
|
*/
|
|
ENTRY(fsave)
|
|
tstw _fppstate | is fpp present and enabled?
|
|
jle 1f | branch if not
|
|
movl sp@(4), a0 | a0 gets destination address
|
|
fsave a0@ | save internal state
|
|
1:
|
|
rts
|
|
|
|
/*
|
|
* fpiar() -- return the contents of the FPP fpiar register
|
|
*/
|
|
ENTRY(fpiar)
|
|
clrl d0 | return 0 if fpp not present
|
|
tstw _fppstate | is fpp present and enabled?
|
|
jle 1f | branch if not
|
|
fmovel fpiar,d0 | return FPIAR
|
|
1:
|
|
rts
|
|
|
|
/*
|
|
* Restore the internal FPP (68881/68882) state from a buffer.
|
|
*/
|
|
ENTRY(frestore)
|
|
tstw _fppstate | is fpp present and enabled?
|
|
jle 1f | branch if not
|
|
movl sp@(4),a0 | source address
|
|
frestore a0@ | restore internal state
|
|
1:
|
|
rts
|
|
|
|
/*
|
|
* Return the primitive returned by reading the coprocessor response CIR.
|
|
* Useful on coprocessor protocol error to find cause of error.
|
|
* If the protocol violation is detected by the FPCP due to an unexpected
|
|
* access, the operation being executed previously is aborted and the FPCP
|
|
* assumes the idle state when the exception acknowledge is received.
|
|
* Thus the primitive read fromn th te response CIR is null (bit-15,CA=0).
|
|
* However, if the protocol violation is detected by the MPU due to
|
|
* illegal primitive, the FPCP response CIR contains that primitive.
|
|
* Since the 68881/68882 aren't supposed to be able to return a illegal
|
|
* primitive, this indicates a nasty hardware problem.
|
|
*/
|
|
ENTRY(get_cir)
|
|
tstw _fppstate | is fpp present and enabled?
|
|
jle 1f | branch if not
|
|
movc sfc, d1
|
|
moveq #FC_CPU,d0 | CPU space to talk to coprocessor
|
|
movc d0,sfc
|
|
movsw 0x00022000,d0 | read response CIR
|
|
movc d1, sfc | restore old sfc
|
|
1:
|
|
rts
|
|
|
|
#endif FPU
|
|
|
|
/*
|
|
* Enable bit in both Unix _enablereg and hard ENABLEREG.
|
|
* on_enablereg(bit) turns on enable register by oring it and bit.
|
|
* E.g. on_enablereg((u_char)ENA_FPA)
|
|
*/
|
|
ENTRY(on_enablereg)
|
|
movw sp@(6),d0 | get word
|
|
orw d0,_enablereg | turn on a bit
|
|
movw _enablereg,ENABLEREG | put it in the hardware
|
|
rts
|
|
|
|
/*
|
|
* Disable bit in both Unix _enablereg and hard ENABLEREG.
|
|
* off_enablereg(bit) turns off enable register by anding it and bit.
|
|
* E.g. off_enablereg((u_char)ENA_FPA)
|
|
*/
|
|
ENTRY(off_enablereg)
|
|
movw sp@(6),d0 | get word
|
|
notw d0 | ~bit
|
|
andw d0,_enablereg | turn off a bit
|
|
movw _enablereg,ENABLEREG | put it in the hardware
|
|
rts
|
|
|
|
/*
|
|
* if a() calls b() calls caller(), caller() returns return address in a().
|
|
*
|
|
* func_t caller()
|
|
*/
|
|
ENTRY(caller)
|
|
movl a6@(4),d0
|
|
rts
|
|
|
|
/*
|
|
* if a() calls callee(), callee() returns the return address in a();
|
|
*
|
|
* func_t callee()
|
|
*/
|
|
ENTRY(callee)
|
|
movl sp@,d0
|
|
rts
|
|
|
|
/* STUFF WHICH ORIGINALLY APPEARED IN 4.1 */
|
|
|
|
/*
|
|
* start_child(parentp, parentu, childp, nfp, new_context, seg)
|
|
* struct proc *parentp, *childp;
|
|
* struct user *parentu;
|
|
* int *new_context;
|
|
* struct file **nfp;
|
|
* struct seguser *seg;
|
|
* This saves the parent's state such that when resume'd, the
|
|
* parent will return to the caller of startchild.
|
|
* It then calls conschild to create the child process
|
|
*
|
|
* We always resume the parent in the kernel.
|
|
* The child usually resumes in userland.
|
|
*
|
|
* FPA exceptions not dealt with (not needed until this is a
|
|
* generic context switching mechanism) (XXX)
|
|
*/
|
|
#define SAVREGS d2-d7/a2-a7
|
|
.globl _cons_child
|
|
.globl _masterprocp
|
|
ENTRY(start_child)
|
|
movl _uunix, a0
|
|
|
|
|
| save 68881 floating point state of parent if used
|
|
|
|
|
tstw _fppstate | is fpp present and enabled?
|
|
jle 1f | branch if not
|
|
fsave a0@(U_FP_ISTATE) | save internal state
|
|
tstb a0@(U_FP_ISTATE) | null state? (vers == FPIS_VERSNULL)
|
|
jeq 1f | branch if so
|
|
fmovem fpc/fps/fpi,a0@(U_FPS_CTRL) | save control registers
|
|
fmovem fp0-fp7,a0@(U_FPS_REGS) | save fp data registers
|
|
| XXX - fpprocp not used until we get rid of frestores with null state
|
|
| movl _masterprocp,_fpprocp | remember whose regs are still loaded
|
|
1:
|
|
|
|
|
|
|
| save parent's kernel regs in pcb so when parent is resumed,
|
|
| it will appear that he just returned from resume.
|
|
|
|
|
movl sp@,a0@(PCB_REGS) | save return pc in pcb
|
|
movw sr,a0@(PCB_SR+2) | save the current psw in u area
|
|
moveml SAVREGS,a0@(PCB_REGS+4) | save data/address regs
|
|
tstl _masterprocp | have a current process?
|
|
jeq 1f | skip if not
|
|
movl _masterprocp,a2 | does it
|
|
movl a2@(P_AS),d0 | have an address space?
|
|
jeq 1f | skip if not
|
|
movl d0,sp@- | find out memory claim for
|
|
jsr _rm_asrss | as associated with this process
|
|
addqw #4,sp | clean up stack
|
|
movl d0,a2@(P_RSSIZE) | store with process
|
|
movl _uunix,a0 | restore a0
|
|
1:
|
|
|
|
|
|
|
| save FPA state of parent if used
|
|
|
|
|
#if NFPA > 0
|
|
/*
|
|
* fpa_save() is inlined here to moveml 12 regs in two instructions.
|
|
* We use a2 to contain the fpa address so that it is preserved across
|
|
* the call to fpa_shutdown and we pick out SAVEFPA regs so that a2
|
|
* is not used and we have a total of 12 regs.
|
|
*/
|
|
#define SAVEFPA d0-d7/a3-a6
|
|
#define FPA_MAXLOOP 512
|
|
|
|
tstw a0@(U_FPA_FLAGS) | test if this process uses FPA
|
|
jeq 1f | FPA is not used skip saving FPA regs
|
|
| Begin of assembly fpa_save
|
|
movl #FPA_MAXLOOP-1,d1 | loop at most FPA_MAXLOOP times
|
|
movl _fpa,a2 | get fpa adddress
|
|
0:
|
|
movl a2@(FPA_PIPE_STATUS),d0 | get status
|
|
andl #FPA_STABLE,d0 | test for stability
|
|
bne 2f | != 0 means pipe is stable
|
|
dbra d1,0b | busy, try again
|
|
jsr _fpa_shutdown | timed out, shut it down
|
|
jra 1f | skip reg save
|
|
2:
|
|
moveml a2@(FPA_STATE),SAVEFPA | load 12 FPA regs
|
|
moveml SAVEFPA,a0@(U_FPA_STATUS) | save to u.u_fpa_status
|
|
1:
|
|
#endif NFPA > 0
|
|
|
|
movl sp, a1
|
|
movl a1@(24), sp@- | seg
|
|
movl a1@(20), sp@- | new_context
|
|
movl a1@(16), sp@- | nfp
|
|
movl a1@(12), sp@- | childp
|
|
movl a1@(8), sp@- | parentu
|
|
movl a1@(4), sp@- | parentp
|
|
jsr _cons_child | create child and switch u areas
|
|
| and reset masterprocp
|
|
/* NOTREACHED */
|
|
|
|
/*
|
|
* yield_child(parent_ar0, child_stack, childp, parent_pid, childu, seg)
|
|
* essentially the second half of resume.
|
|
* Need to push state onto child's stack such that ksp
|
|
* will be at the top of the child's stack when the rte happens.
|
|
* MUST be called at splclock().
|
|
*/
|
|
ENTRY(yield_child)
|
|
movl sp@(12), _masterprocp
|
|
movl _masterprocp, a1
|
|
|
|
/*
|
|
* Check to see if we already have context. If so, then set up
|
|
* to use the context otherwise to default to the kernel context.
|
|
*/
|
|
movl a1@(P_AS),d0 | get p->p_as
|
|
jeq 0f | skip ahead if NULL (kernel process)
|
|
movl d0,a0
|
|
movb a0@(A_HAT_VLD),d0 | p->p_as->a_hat.hat_valid
|
|
jeq 0f | skip ahead if NULL (no ctx allocated)
|
|
|
|
| XXX - this stuff needs more work...
|
|
movl a0@(A_HAT_PFN),d0 | get level 1 pfn
|
|
andl #HAT_PFNMASK,d0 | mask off valid byte
|
|
movl #MMU_PAGESHIFT,d1 | put shift count in d1 (it's > 8)
|
|
asll d1,d0 | shift up to get u area addr
|
|
bra 1f
|
|
0:
|
|
moveq #0,d0 | use kernel's "context"
|
|
1:
|
|
addl #PCB_L1PT,d0 | add offset to get level 1 page table
|
|
movl d0,rtptr+4 | fill software copy with address
|
|
pmoveflush rtptr,crp | load hardware register (flushes ATC)
|
|
| XXX - end of this stuff needs more work...
|
|
|
|
movl _uunix, a0
|
|
tstw _fppstate | is fpp present and enabled?
|
|
jle 1f | branch past fpp and fpa code if not
|
|
tstb a0@(U_FP_ISTATE) | null state? (vers == FPIS_VERSNULL)
|
|
jeq 0f | branch if so
|
|
| XXX - fpprocp not used until we get rid of frestores with null state
|
|
| cmpl _fpprocp,a1 | check if we were last proc using fpp
|
|
| beq 0f | if so, jump and skip loading ext regs
|
|
fmovem a0@(U_FPS_REGS),fp0-fp7 | restore fp data registers
|
|
fmovem a0@(U_FPS_CTRL),fpc/fps/fpi | restore control registers
|
|
0:
|
|
frestore a0@(U_FP_ISTATE) | restore internal state
|
|
|
|
#if NFPA > 0
|
|
tstw a0@(U_FPA_FLAGS) | test if this process uses FPA
|
|
jeq 1f | no, skip restoring FPA context
|
|
movl sp@(20), sp@- | childu
|
|
jsr _fpa_restore | yes, restore FPA context
|
|
addqw #4, sp
|
|
#endif NFPA > 0
|
|
1:
|
|
|
|
movl #ICACHE_CLEAR+ICACHE_ENABLE+DCACHE_CLEAR+DCACHE_ENABLE,d0
|
|
movc d0,cacr | clear (and enable) the cache;
|
|
| we're switching contexts.
|
|
|
|
movl sp@(8), a2 | child's stack top
|
|
tstl a2@(-4) | kernel process?
|
|
jne userproc | no
|
|
|
|
| kernel process. Align on even boundary since we never return
|
|
| to user land only to push an odd # of shorts on the stacks
|
|
| upon reentry into the kernel.
|
|
| Note that the argument needs to be swapped since we use 0-non-0 to
|
|
| tell if a kernel process or not and use the stack top
|
|
| for this info.
|
|
movl a2@(-8), a2@(-4) | swap argument into correct place
|
|
movl #exit1, a2@(-8) | fall thru to exit
|
|
subl #0x10, sp@(8) | point to rte stuff (long aligned)
|
|
jra changectx
|
|
|
|
| user process. Align on odd boundary since reentry into the kernel
|
|
| pushes an odd number of shorts.
|
|
| See userstackset() for details. We also have to get the user's
|
|
| sp; we get this from the state saved at system call entry time
|
|
| in ar0-indexed registers.
|
|
userproc:
|
|
movl sp@(4), a0 | parent's frame in a0
|
|
movl a0@(R_SP), a1 | parent's usp
|
|
addl #4, a1 | pop syscall argument
|
|
movl a1, usp | load usp
|
|
subl #0xa, sp@(8) | point to rte stuff (short aligned)
|
|
|
|
changectx:
|
|
moveml a0@(8), d2-d7/a0-a6 | restore parent's regs except d0/d1/sp
|
|
movl sp@(16), d0 | rval1 (pid)
|
|
movl sp@(20), _uunix | switch u area
|
|
moveq #1, d1 | rval2 (1)
|
|
movl sp@(8), sp | install child's stack
|
|
rte
|
|
|
|
.globl _exit
|
|
exit1:
|
|
movl #0, sp@- | argument to exit
|
|
jsr _exit | exit(0)
|
|
/* NOTREACHED */
|
|
|
|
/*
|
|
* When a process is created by main to do init, it starts here.
|
|
* We hack the stack to make it look like a system call frame.
|
|
* Then, icode exec's init. We "resume" init directly via rei.
|
|
* Leave extra short on kernel stack so traps come in long aligned.
|
|
*/
|
|
.globl _icode
|
|
.globl _icode1
|
|
_icode1:
|
|
pea 0x10000 | non-zero pad + dummy fmt & vor
|
|
pea USRTEXT | push pc
|
|
movw #SR_USER, sp@- | push sr
|
|
lea 0, a6 | stack frame link 0 in main
|
|
SAVEALL()
|
|
jsr _icode
|
|
clrl d0 | fake return value from trap
|
|
jra rei
|
|
|
|
/*
|
|
* finishexit(vaddr)
|
|
* Done with the current stack;
|
|
* switch to the exit stack, clear masterprocp, segu_release, and swtch.
|
|
*/
|
|
.globl _segu_release
|
|
ENTRY(finishexit)
|
|
orw #SR_INTPRI,sr | lock out interrupts
|
|
movl sp@(4), a0 | get arg for segu_release
|
|
cmpl #eintstack, sp | shouldn't be on interrupt
|
|
| stack: will lose info if so
|
|
jls 1f | yes: too bad
|
|
lea eexitstack, sp | switch to exit stack
|
|
movl #intu, _uunix | need some u area -- e.g. swtch
|
|
movl #1, _noproc | no process active now
|
|
clrl _masterprocp | no more "current process"
|
|
andw #~SR_INTPRI,sr | ok to take interrupts now
|
|
movl a0, sp@- | arg to segu_release
|
|
jsr _segu_release | free u area and stack
|
|
addqw #4,sp | pop segu_release's arg
|
|
jsr _swtch | context switch
|
|
/* NOTREACHED */
|
|
|
|
1: pea 2f
|
|
jsr _panic | panic("finishexit");
|
|
/* NOTREACHED */
|
|
|
|
.data
|
|
2: .asciz "finishexit"
|
|
.even
|
|
.text
|
|
|
|
/*
|
|
* setmmucntxt(parentp)
|
|
*/
|
|
ENTRY(setmmucntxt)
|
|
movl sp@(4), a1 | new process
|
|
movl a1@(P_AS),d0 | get p->p_as
|
|
jeq 0f | skip ahead if NULL (kernel process)
|
|
movl d0,a0
|
|
movl a0@(A_HAT_VLD),d0 | get as->a_hat.hat_valid
|
|
jeq 0f | skip ahead if NULL (no ctx allocated)
|
|
| XXX needs more work...
|
|
movl a0@(A_HAT_PFN),d0 | get level 1 pfn
|
|
andl #HAT_PFNMASK,d0 | mask off valid byte
|
|
movl #MMU_PAGESHIFT,d1 | put shift count in d1 (it's > 8)
|
|
asll d1,d0 | shift up to get u area addr
|
|
bra 1f
|
|
0:
|
|
moveq #0,d0 | use kernel's "context"
|
|
1:
|
|
addl #PCB_L1PT,d0 | add offset to get level 1 page table
|
|
movl d0,rtptr+4 | fill software copy with address
|
|
pmoveflush rtptr,crp | load hardware register (flushes ATC)
|
|
|
|
/*
|
|
* Now that the u areas are at different virtual addresses,
|
|
* we don't need to flush the on-chip data cache here because
|
|
* of any mappings changing in the kernel's address space.
|
|
* However, we need to flush this cache here in case there
|
|
* is anything from the user's address space in the cache.
|
|
*/
|
|
movl #ICACHE_ENABLE+DCACHE_CLEAR+DCACHE_ENABLE,d0
|
|
movc d0,cacr | clear the data cache
|
|
|
|
rts
|
|
|
|
/* END OF STUFF WHICH ORIGINALLY APPEARED IN 4.1 */
|
|
|
|
/*
|
|
* Flush MMU TLB entry.
|
|
*/
|
|
ENTRY(atc_flush_entry)
|
|
movl sp@(4),a0 | get the address to flush
|
|
pflush #0,#0,a0@ | flush, ignoring function codes
|
|
rts
|
|
|
|
/*
|
|
* Flush MMU TLB.
|
|
*/
|
|
ENTRY(atc_flush_all)
|
|
pflusha | flushes entire TLB
|
|
rts
|
|
|
|
.data
|
|
.align 4 | space for root pointer construction
|
|
.globl rtptr
|
|
rtptr: .long 0x7FFF0003 | no limit, points to long descriptor
|
|
.long 0 | address filled in later
|
|
.text
|
|
|
|
/*
|
|
* Get MMU root pointer.
|
|
*/
|
|
ENTRY(mmu_getrootptr)
|
|
movl rtptr+4,d0
|
|
rts
|
|
|
|
/*
|
|
* Set MMU root pointer.
|
|
*/
|
|
ENTRY(mmu_setrootptr)
|
|
movl sp@(4),rtptr+4 | fill software copy with address
|
|
pmoveflush rtptr,crp | load hardware register
|
|
rts
|
|
|
|
/*
|
|
* Get the MMU status register from a fault.
|
|
*/
|
|
ENTRY(mmu_geterror)
|
|
movl sp@(4),a0 | get virtual address
|
|
movl sp@(8),d0 | get function code
|
|
tstl sp@(0xc) | test if cycle was a read
|
|
bne 1f | go to write case if not
|
|
ptestr d0,a0@,#3 | test the read case
|
|
bra 2f
|
|
1:
|
|
ptestw d0,a0@,#3 | test the write case
|
|
2:
|
|
pmove psr,sp@(4) | get status out of mmu
|
|
movw sp@(4),d0 | status is return value
|
|
rts
|
|
|
|
/*
|
|
* Flush the on chip data cache.
|
|
*/
|
|
ENTRY(vac_flush)
|
|
movl #ICACHE_ENABLE+DCACHE_CLEAR+DCACHE_ENABLE,d0
|
|
movc d0,cacr | clear the data cache
|
|
rts
|
|
|
|
#ifdef PARITY
|
|
/*
|
|
* Put parity error for testing parity recovery.
|
|
*/
|
|
ENTRY(pperr)
|
|
movl sp@(4),a0
|
|
movb a0@,d0
|
|
movb d0,a0@
|
|
orb #0x20,0xfedf9000:l
|
|
movb d0,a0@
|
|
andb #0xdf,0xfedf9000:l
|
|
rts
|
|
#endif PARITY
|
|
|
|
/*
|
|
* Define some variables used by post-mortem debuggers
|
|
* to help them work on kernels with changing structures.
|
|
*/
|
|
.globl UPAGES_DEBUG, KERNELBASE_DEBUG, VADDR_MASK_DEBUG
|
|
.globl PGSHIFT_DEBUG, SLOAD_DEBUG
|
|
|
|
UPAGES_DEBUG = UPAGES
|
|
KERNELBASE_DEBUG = KERNELBASE
|
|
VADDR_MASK_DEBUG = 0xffffffff
|
|
PGSHIFT_DEBUG = PGSHIFT
|
|
SLOAD_DEBUG = SLOAD
|