/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #if defined(lint) #include #include #include #include #endif /* lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(lint) #include #include #else /* lint */ #include "assym.h" ! ! REGOFF must add up to allow double word access to r_tstate. ! PCB_WBUF must also be aligned. ! #if (REGOFF & 7) != 0 #error "struct regs not aligned" #endif /* * Absolute external symbols. * On the sun4u we put the panic buffer in the third and fourth pages. * We set things up so that the first 2 pages of KERNELBASE is illegal * to act as a redzone during copyin/copyout type operations. One of * the reasons the panic buffer is allocated in low memory to * prevent being overwritten during booting operations (besides * the fact that it is small enough to share pages with others). */ .seg ".data" .global panicbuf PROM = 0xFFE00000 ! address of prom virtual area panicbuf = SYSBASE32 + PAGESIZE ! address of panic buffer .type panicbuf, #object .size panicbuf, PANICBUFSIZE /* * Absolute external symbol - intr_vec_table. * * With new bus structures supporting a larger number of interrupt * numbers, the interrupt vector table, intr_vec_table[] has been * moved out of kernel nucleus and allocated after panicbuf. */ .global intr_vec_table intr_vec_table = SYSBASE32 + PAGESIZE + PANICBUFSIZE ! address of interrupt table .type intr_vec_table, #object .size intr_vec_table, MAXIVNUM * CPTRSIZE + MAX_RSVD_IV * IV_SIZE + MAX_RSVD_IVX * (IV_SIZE + CPTRSIZE * (NCPU - 1)) /* * The thread 0 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 if the stack overflows. We get a * red zone below this stack for free when the kernel text is * write protected. */ .global t0stack .align 16 .type t0stack, #object t0stack: .skip T0STKSZ ! thread 0 stack t0stacktop: .size t0stack, T0STKSZ /* * cpu0 and its ptl1_panic stack. The cpu structure must be allocated * on a single page for ptl1_panic's physical address accesses. */ .global cpu0 .align MMU_PAGESIZE cpu0: .type cpu0, #object .skip CPU_ALLOC_SIZE .size cpu0, CPU_ALLOC_SIZE .global t0 .align PTR24_ALIGN ! alignment for mutex. .type t0, #object t0: .skip THREAD_SIZE ! thread 0 .size t0, THREAD_SIZE #ifdef TRAPTRACE .global trap_trace_ctl .global trap_tr0 .global trap_trace_bufsize .global trap_freeze .global trap_freeze_pc .align 4 trap_trace_bufsize: .word TRAP_TSIZE ! default trap buffer size trap_freeze: .word 0 .align 64 trap_trace_ctl: .skip NCPU * TRAPTR_SIZE ! NCPU control headers .align 16 trap_tr0: .skip TRAP_TSIZE ! one buffer for the boot cpu /* * When an assertion in TRACE_PTR was failed, %pc is saved in trap_freeze_pc to * show in which TRACE_PTR the assertion failure happened. */ .align 8 trap_freeze_pc: .nword 0 #endif /* TRAPTRACE */ .align 4 .seg ".text" #ifdef NOPROM .global availmem availmem: .word 0 #endif /* NOPROM */ .align 8 _local_p1275cis: .nword 0 #endif /* lint */ #if defined(lint) void _start(void) {} #else /* lint */ .seg ".data" .global nwindows, nwin_minus_one, winmask nwindows: .word 8 nwin_minus_one: .word 7 winmask: .word 8 .global afsrbuf afsrbuf: .word 0,0,0,0 /* * System initialization * * Our contract with the boot prom specifies that the MMU is on and the * first 16 meg of memory is mapped with a level-1 pte. We are called * with p1275cis ptr in %o0 and kdi_dvec in %o1; we start execution * directly from physical memory, so we need to get up into our proper * addresses quickly: all code before we do this must be position * independent. * * NB: Above is not true for boot/stick kernel, the only thing mapped is * the text+data+bss. The kernel is loaded directly into KERNELBASE. * * entry, the romvec pointer (romp) is the first argument; * i.e., %o0. * the bootops vector is in the third argument (%o1) * * Our tasks are: * save parameters * construct mappings for KERNELBASE (not needed for boot/stick kernel) * hop up into high memory (not needed for boot/stick kernel) * initialize stack pointer * initialize trap base register * initialize window invalid mask * initialize psr (with traps enabled) * figure out all the module type stuff * tear down the 1-1 mappings * dive into main() */ ENTRY_NP(_start) ! ! Stash away our arguments in memory. ! sethi %hi(_local_p1275cis), %g1 stn %o4, [%g1 + %lo(_local_p1275cis)] ! ! Initialize CPU state registers ! wrpr %g0, PSTATE_KERN, %pstate wr %g0, %g0, %fprs ! ! call krtld to link the world together ! call kobj_start mov %o4, %o0 CLEARTICKNPT ! allow user rdtick ! ! Get maxwin from %ver ! rdpr %ver, %g1 and %g1, VER_MAXWIN, %g1 ! ! Stuff some memory cells related to numbers of windows. ! sethi %hi(nwin_minus_one), %g2 st %g1, [%g2 + %lo(nwin_minus_one)] inc %g1 sethi %hi(nwindows), %g2 st %g1, [%g2 + %lo(nwindows)] dec %g1 mov -2, %g2 sll %g2, %g1, %g2 sethi %hi(winmask), %g4 st %g2, [%g4 + %lo(winmask)] ! ! save a pointer to obp's tba for later use by kmdb ! rdpr %tba, %g1 set boot_tba, %g2 stx %g1, [%g2] ! ! copy obp's breakpoint trap entry to obp_bpt ! rdpr %tba, %g1 set T_SOFTWARE_TRAP | ST_MON_BREAKPOINT, %g2 sll %g2, 5, %g2 or %g1, %g2, %g1 set obp_bpt, %g2 ldx [%g1], %g3 stx %g3, [%g2] flush %g2 ldx [%g1 + 8], %g3 stx %g3, [%g2 + 8] flush %g2 + 8 ldx [%g1 + 16], %g3 stx %g3, [%g2 + 16] flush %g2 + 16 ldx [%g1 + 24], %g3 stx %g3, [%g2 + 24] flush %g2 + 24 ! ! Initialize thread 0's stack. ! set t0stacktop, %g1 ! setup kernel stack pointer sub %g1, SA(KFPUSIZE+GSR_SIZE), %g2 and %g2, 0x3f, %g3 sub %g2, %g3, %o1 sub %o1, SA(MPCBSIZE) + STACK_BIAS, %sp ! ! Initialize global thread register. ! set t0, THREAD_REG ! ! Fill in enough of the cpu structure so that ! the wbuf management code works. Make sure the ! boot cpu is inserted in cpu[] based on cpuid. ! CPU_INDEX(%g2, %g1) sll %g2, CPTRSHIFT, %g2 ! convert cpuid to cpu[] offset set cpu0, %o0 ! &cpu0 set cpu, %g1 ! &cpu[] stn %o0, [%g1 + %g2] ! cpu[cpuid] = &cpu0 stn %o0, [THREAD_REG + T_CPU] ! threadp()->t_cpu = cpu[cpuid] stn THREAD_REG, [%o0 + CPU_THREAD] ! cpu[cpuid]->cpu_thread = threadp() ! We do NOT need to bzero our BSS...boot has already done it for us. ! Just need to reference edata so that we don't break /dev/ksyms set edata, %g0 ! ! Call mlsetup with address of prototype user registers. ! call mlsetup add %sp, REGOFF + STACK_BIAS, %o0 #if (REGOFF != MPCB_REGS) #error "hole in struct machpcb between frame and regs?" #endif ! ! Now call main. We will return as process 1 (init). ! call main nop ! ! Main should never return. ! set .mainretmsg, %o0 call panic nop SET_SIZE(_start) .mainretmsg: .asciz "main returned" .align 4 #endif /* lint */ /* * Generic system trap handler. * * Some kernel trap handlers save themselves from buying a window by * borrowing some of sys_trap's unused locals. %l0 thru %l3 may be used * for this purpose, as user_rtt and priv_rtt do not depend on them. * %l4 thru %l7 should NOT be used this way. * * Entry Conditions: * %pstate am:0 priv:1 ie:0 * globals are either ag or ig (not mg!) * * Register Inputs: * %g1 pc of trap handler * %g2, %g3 args for handler * %g4 desired %pil (-1 means current %pil) * %g5, %g6 destroyed * %g7 saved * * Register Usage: * %l0, %l1 temps * %l3 saved %g1 * %l6 curthread for user traps, %pil for priv traps * %l7 regs * * Called function prototype variants: * * func(struct regs *rp); * func(struct regs *rp, uintptr_t arg1 [%g2], uintptr_t arg2 [%g3]) * func(struct regs *rp, uintptr_t arg1 [%g2], * uint32_t arg2 [%g3.l], uint32_t arg3 [%g3.h]) * func(struct regs *rp, uint32_t arg1 [%g2.l], * uint32_t arg2 [%g3.l], uint32_t arg3 [%g3.h], uint32_t [%g2.h]) */ #if defined(lint) void sys_trap(void) {} #else /* lint */ ENTRY_NP(sys_trap) ! ! force tl=1, update %cwp, branch to correct handler ! wrpr %g0, 1, %tl rdpr %tstate, %g5 btst TSTATE_PRIV, %g5 and %g5, TSTATE_CWP, %g6 bnz,pn %xcc, priv_trap wrpr %g0, %g6, %cwp ALTENTRY(user_trap) ! ! user trap ! ! make all windows clean for kernel ! buy a window using the current thread's stack ! sethi %hi(nwin_minus_one), %g5 ld [%g5 + %lo(nwin_minus_one)], %g5 wrpr %g0, %g5, %cleanwin CPU_ADDR(%g5, %g6) ldn [%g5 + CPU_THREAD], %g5 ldn [%g5 + T_STACK], %g6 sub %g6, STACK_BIAS, %g6 save %g6, 0, %sp ! ! set window registers so that current windows are "other" windows ! rdpr %canrestore, %l0 rdpr %wstate, %l1 wrpr %g0, 0, %canrestore sllx %l1, WSTATE_SHIFT, %l1 wrpr %l1, WSTATE_K64, %wstate wrpr %g0, %l0, %otherwin ! ! set pcontext to run kernel ! sethi %hi(kcontextreg), %l0 ldx [%l0 + %lo(kcontextreg)], %l0 mov MMU_PCONTEXT, %l1 ! if kcontextreg==PCONTEXT, do nothing ldxa [%l1]ASI_MMU_CTX, %l2 xor %l0, %l2, %l2 srlx %l2, CTXREG_NEXT_SHIFT, %l2 brz %l2, 2f ! if N_pgsz0/1 changed, need demap sethi %hi(FLUSH_ADDR), %l3 mov DEMAP_ALL_TYPE, %l2 stxa %g0, [%l2]ASI_DTLB_DEMAP stxa %g0, [%l2]ASI_ITLB_DEMAP 2: stxa %l0, [%l1]ASI_MMU_CTX flush %l3 ! flush required by immu 1: set utl0, %g6 ! bounce to utl0 have_win: SYSTRAP_TRACE(%o1, %o2, %o3) ! ! at this point we have a new window we can play in, ! and %g6 is the label we want done to bounce to ! ! save needed current globals ! mov %g1, %l3 ! pc mov %g2, %o1 ! arg #1 mov %g3, %o2 ! arg #2 srlx %g3, 32, %o3 ! pseudo arg #3 srlx %g2, 32, %o4 ! pseudo arg #4 mov %g5, %l6 ! curthread if user trap, %pil if priv trap ! ! save trap state on stack ! add %sp, REGOFF + STACK_BIAS, %l7 rdpr %tpc, %l0 rdpr %tnpc, %l1 rdpr %tstate, %l2 stn %l0, [%l7 + PC_OFF] stn %l1, [%l7 + nPC_OFF] stx %l2, [%l7 + TSTATE_OFF] ! ! setup pil ! brlz,pt %g4, 1f nop #ifdef DEBUG ! ! ASSERT(%g4 >= %pil). ! rdpr %pil, %l0 cmp %g4, %l0 bge,pt %xcc, 0f nop ! yes, nop; to avoid anull set bad_g4_called, %l3 mov 1, %o1 st %o1, [%l3] set bad_g4, %l3 ! pc set sys_trap_wrong_pil, %o1 ! arg #1 mov %g4, %o2 ! arg #2 ba 1f ! stay at the current %pil mov %l0, %o3 ! arg #3 0: #endif /* DEBUG */ wrpr %g0, %g4, %pil 1: ! ! set trap regs to execute in kernel at %g6 ! done resumes execution there ! wrpr %g0, %g6, %tnpc rdpr %cwp, %l0 set TSTATE_KERN, %l1 wrpr %l1, %l0, %tstate done /* NOTREACHED */ SET_SIZE(user_trap) SET_SIZE(sys_trap) ENTRY_NP(prom_trap) ! ! prom trap switches the stack to 32-bit ! if we took a trap from a 64-bit window ! Then buys a window on the current stack. ! save %sp, -SA64(REGOFF + REGSIZE), %sp /* 32 bit frame, 64 bit sized */ set ptl0, %g6 ba,a,pt %xcc, have_win SET_SIZE(prom_trap) ENTRY_NP(priv_trap) ! ! kernel trap ! buy a window on the current stack ! ! is the trap PC in the range allocated to Open Firmware? rdpr %tpc, %g5 set OFW_END_ADDR, %g6 cmp %g5, %g6 bgu,a,pn %xcc, 1f rdpr %pil, %g5 set OFW_START_ADDR, %g6 cmp %g5, %g6 bgeu,pn %xcc, prom_trap rdpr %pil, %g5 1: ! ! check if the primary context is of kernel. ! mov MMU_PCONTEXT, %g6 ldxa [%g6]ASI_MMU_CTX, %g5 sllx %g5, CTXREG_CTX_SHIFT, %g5 ! keep just the ctx bits brnz,pn %g5, 2f ! assumes KCONTEXT == 0 rdpr %pil, %g5 ! ! primary context is of kernel. ! set ktl0, %g6 save %sp, -SA(REGOFF + REGSIZE), %sp ba,a,pt %xcc, have_win 2: ! ! primary context is of user. caller of sys_trap() ! or priv_trap() did not set kernel context. raise ! trap level to MAXTL-1 so that ptl1_panic() prints ! out all levels of trap data. ! rdpr %ver, %g5 srlx %g5, VER_MAXTL_SHIFT, %g5 and %g5, VER_MAXTL_MASK, %g5 ! %g5 = MAXTL sub %g5, 1, %g5 wrpr %g0, %g5, %tl mov PTL1_BAD_CTX, %g1 ba,a,pt %xcc, ptl1_panic SET_SIZE(priv_trap) ENTRY_NP(utl0) SAVE_GLOBALS(%l7) SAVE_OUTS(%l7) mov %l6, THREAD_REG wrpr %g0, PSTATE_KERN, %pstate ! enable ints jmpl %l3, %o7 ! call trap handler mov %l7, %o0 ! ALTENTRY(user_rtt) ! ! Register inputs ! %l7 - regs ! ! disable interrupts and check for ASTs and wbuf restores ! keep cpu_base_spl in %l4 and THREAD_REG in %l6 (needed ! in wbuf.s when globals have already been restored). ! wrpr %g0, PIL_MAX, %pil ldn [THREAD_REG + T_CPU], %l0 ld [%l0 + CPU_BASE_SPL], %l4 ldub [THREAD_REG + T_ASTFLAG], %l2 brz,pt %l2, 1f ld [%sp + STACK_BIAS + MPCB_WBCNT], %l3 ! ! call trap to do ast processing ! wrpr %g0, %l4, %pil ! pil = cpu_base_spl mov %l7, %o0 call trap mov T_AST, %o2 ba,a,pt %xcc, user_rtt 1: brz,pt %l3, 2f mov THREAD_REG, %l6 ! ! call restore_wbuf to push wbuf windows to stack ! wrpr %g0, %l4, %pil ! pil = cpu_base_spl mov %l7, %o0 call trap mov T_FLUSH_PCB, %o2 ba,a,pt %xcc, user_rtt 2: #ifdef TRAPTRACE TRACE_RTT(TT_SYS_RTT_USER, %l0, %l1, %l2, %l3) #endif /* TRAPTRACE */ ld [%sp + STACK_BIAS + MPCB_WSTATE], %l3 ! get wstate ! ! restore user globals and outs ! rdpr %pstate, %l1 wrpr %l1, PSTATE_IE, %pstate RESTORE_GLOBALS(%l7) ! switch to alternate globals, saving THREAD_REG in %l6 wrpr %l1, PSTATE_IE | PSTATE_AG, %pstate mov %sp, %g6 ! remember the mpcb pointer in %g6 RESTORE_OUTS(%l7) ! ! set %pil from cpu_base_spl ! wrpr %g0, %l4, %pil ! ! raise tl (now using nucleus context) ! wrpr %g0, 1, %tl ! switch "other" windows back to "normal" windows. rdpr %otherwin, %g1 wrpr %g0, 0, %otherwin add %l3, WSTATE_CLEAN_OFFSET, %l3 ! convert to "clean" wstate wrpr %g0, %l3, %wstate wrpr %g0, %g1, %canrestore ! set pcontext to scontext for user execution mov MMU_SCONTEXT, %g3 ldxa [%g3]ASI_MMU_CTX, %g2 mov MMU_PCONTEXT, %g3 ldxa [%g3]ASI_MMU_CTX, %g4 ! need N_pgsz0/1 bits srlx %g4, CTXREG_NEXT_SHIFT, %g4 sllx %g4, CTXREG_NEXT_SHIFT, %g4 or %g4, %g2, %g2 ! Or in Nuc pgsz bits sethi %hi(FLUSH_ADDR), %g4 stxa %g2, [%g3]ASI_MMU_CTX flush %g4 ! flush required by immu ! ! Within the code segment [rtt_ctx_start - rtt_ctx_end], ! PCONTEXT is set to run user code. If a trap happens in this ! window, and the trap needs to be handled at TL=0, the handler ! must make sure to set PCONTEXT to run kernel. A convenience ! macro, RESET_USER_RTT_REGS(scr1, scr2, label) is available to ! TL>1 handlers for this purpose. ! ! %g1 = %canrestore ! %l7 = regs ! %g6 = mpcb ! .global rtt_ctx_start rtt_ctx_start: ! ! setup trap regs ! ldn [%l7 + PC_OFF], %g3 ldn [%l7 + nPC_OFF], %g2 ldx [%l7 + TSTATE_OFF], %l0 andn %l0, TSTATE_CWP, %g7 wrpr %g3, %tpc wrpr %g2, %tnpc ! ! Restore to window we originally trapped in. ! First attempt to restore from the watchpoint saved register window ! tst %g1 bne,a 1f clrn [%g6 + STACK_BIAS + MPCB_RSP0] tst %fp be,a 1f clrn [%g6 + STACK_BIAS + MPCB_RSP0] ! test for user return window in pcb ldn [%g6 + STACK_BIAS + MPCB_RSP0], %g1 cmp %fp, %g1 bne 1f clrn [%g6 + STACK_BIAS + MPCB_RSP0] restored restore ! restore from user return window RESTORE_V9WINDOW(%g6 + STACK_BIAS + MPCB_RWIN0) ! ! Attempt to restore from the scond watchpoint saved register window tst %fp be,a 2f clrn [%g6 + STACK_BIAS + MPCB_RSP1] ldn [%g6 + STACK_BIAS + MPCB_RSP1], %g1 cmp %fp, %g1 bne 2f clrn [%g6 + STACK_BIAS + MPCB_RSP1] restored restore RESTORE_V9WINDOW(%g6 + STACK_BIAS + MPCB_RWIN1) save b,a 2f 1: restore ! should not trap 2: ! ! set %cleanwin to %canrestore ! set %tstate to the correct %cwp ! retry resumes user execution ! rdpr %canrestore, %g1 wrpr %g0, %g1, %cleanwin rdpr %cwp, %g1 wrpr %g1, %g7, %tstate retry .global rtt_ctx_end rtt_ctx_end: /* NOTREACHED */ SET_SIZE(user_rtt) SET_SIZE(utl0) ENTRY_NP(ptl0) SAVE_GLOBALS(%l7) SAVE_OUTS(%l7) CPU_ADDR(%g5, %g6) ldn [%g5 + CPU_THREAD], THREAD_REG wrpr %g0, PSTATE_KERN, %pstate ! enable ints jmpl %l3, %o7 ! call trap handler mov %l7, %o0 ! ALTENTRY(prom_rtt) #ifdef TRAPTRACE TRACE_RTT(TT_SYS_RTT_PROM, %l0, %l1, %l2, %l3) #endif /* TRAPTRACE */ ba,pt %xcc, common_rtt mov THREAD_REG, %l0 SET_SIZE(prom_rtt) SET_SIZE(ptl0) ENTRY_NP(ktl0) SAVE_GLOBALS(%l7) SAVE_OUTS(%l7) ! for the call bug workaround wrpr %g0, PSTATE_KERN, %pstate ! enable ints jmpl %l3, %o7 ! call trap handler mov %l7, %o0 ! ALTENTRY(priv_rtt) #ifdef TRAPTRACE TRACE_RTT(TT_SYS_RTT_PRIV, %l0, %l1, %l2, %l3) #endif /* TRAPTRACE */ ! ! Register inputs ! %l7 - regs ! %l6 - trap %pil ! ! Check for a kernel preemption request ! ldn [THREAD_REG + T_CPU], %l0 ldub [%l0 + CPU_KPRUNRUN], %l0 brz,pt %l0, 1f nop ! ! Attempt to preempt ! ldstub [THREAD_REG + T_PREEMPT_LK], %l0 ! load preempt lock brnz,pn %l0, 1f ! can't call kpreempt if this thread is nop ! already in it... call kpreempt mov %l6, %o0 ! pass original interrupt level stub %g0, [THREAD_REG + T_PREEMPT_LK] ! nuke the lock rdpr %pil, %o0 ! compare old pil level cmp %l6, %o0 ! with current pil level movg %xcc, %o0, %l6 ! if current is lower, drop old pil 1: ! ! If we interrupted the mutex_owner_running() critical region we ! must reset the PC and nPC back to the beginning to prevent missed ! wakeups. See the comments in mutex_owner_running() for details. ! ldn [%l7 + PC_OFF], %l0 set mutex_owner_running_critical_start, %l1 sub %l0, %l1, %l0 cmp %l0, mutex_owner_running_critical_size bgeu,pt %xcc, 2f mov THREAD_REG, %l0 stn %l1, [%l7 + PC_OFF] ! restart mutex_owner_running() add %l1, 4, %l1 ba,pt %xcc, common_rtt stn %l1, [%l7 + nPC_OFF] 2: ! ! If we interrupted the mutex_exit() critical region we must reset ! the PC and nPC back to the beginning to prevent missed wakeups. ! See the comments in mutex_exit() for details. ! ldn [%l7 + PC_OFF], %l0 set mutex_exit_critical_start, %l1 sub %l0, %l1, %l0 cmp %l0, mutex_exit_critical_size bgeu,pt %xcc, common_rtt mov THREAD_REG, %l0 stn %l1, [%l7 + PC_OFF] ! restart mutex_exit() add %l1, 4, %l1 stn %l1, [%l7 + nPC_OFF] common_rtt: ! ! restore globals and outs ! rdpr %pstate, %l1 wrpr %l1, PSTATE_IE, %pstate RESTORE_GLOBALS(%l7) ! switch to alternate globals wrpr %l1, PSTATE_IE | PSTATE_AG, %pstate RESTORE_OUTS(%l7) ! ! set %pil from max(old pil, cpu_base_spl) ! ldn [%l0 + T_CPU], %l0 ld [%l0 + CPU_BASE_SPL], %l0 cmp %l6, %l0 movg %xcc, %l6, %l0 wrpr %g0, %l0, %pil ! ! raise tl ! setup trap regs ! restore to window we originally trapped in ! wrpr %g0, 1, %tl ldn [%l7 + PC_OFF], %g1 ldn [%l7 + nPC_OFF], %g2 ldx [%l7 + TSTATE_OFF], %l0 andn %l0, TSTATE_CWP, %g7 wrpr %g1, %tpc wrpr %g2, %tnpc restore ! ! set %tstate to the correct %cwp ! retry resumes prom execution ! rdpr %cwp, %g1 wrpr %g1, %g7, %tstate retry /* NOTREACHED */ SET_SIZE(priv_rtt) SET_SIZE(ktl0) #endif /* lint */ #ifndef lint #ifdef DEBUG .seg ".data" .align 4 .global bad_g4_called bad_g4_called: .word 0 sys_trap_wrong_pil: .asciz "sys_trap: %g4(%d) is lower than %pil(%d)" .align 4 .seg ".text" ENTRY_NP(bad_g4) mov %o1, %o0 mov %o2, %o1 call panic mov %o3, %o2 SET_SIZE(bad_g4) #endif /* DEBUG */ #endif /* lint */ /* * sys_tl1_panic can be called by traps at tl1 which * really want to panic, but need the rearrangement of * the args as provided by this wrapper routine. */ #if defined(lint) void sys_tl1_panic(void) {} #else /* lint */ ENTRY_NP(sys_tl1_panic) mov %o1, %o0 mov %o2, %o1 call panic mov %o3, %o2 SET_SIZE(sys_tl1_panic) #endif /* lint */ /* * Turn on or off bits in the auxiliary i/o register. * * set_auxioreg(bit, flag) * int bit; bit mask in aux i/o reg * int flag; 0 = off, otherwise on * * This is intrinsicly ugly but is used by the floppy driver. It is also * used to turn on/off the led. */ #if defined(lint) /* ARGSUSED */ void set_auxioreg(int bit, int flag) {} #else /* lint */ .seg ".data" .align 4 auxio_panic: .asciz "set_auxioreg: interrupts already disabled on entry" .align 4 .seg ".text" ENTRY_NP(set_auxioreg) /* * o0 = bit mask * o1 = flag: 0 = off, otherwise on * * disable interrupts while updating auxioreg */ rdpr %pstate, %o2 #ifdef DEBUG andcc %o2, PSTATE_IE, %g0 /* if interrupts already */ bnz,a,pt %icc, 1f /* disabled, panic */ nop sethi %hi(auxio_panic), %o0 call panic or %o0, %lo(auxio_panic), %o0 1: #endif /* DEBUG */ wrpr %o2, PSTATE_IE, %pstate /* disable interrupts */ sethi %hi(v_auxio_addr), %o3 ldn [%o3 + %lo(v_auxio_addr)], %o4 ldub [%o4], %g1 /* read aux i/o register */ tst %o1 bnz,a 2f bset %o0, %g1 /* on */ bclr %o0, %g1 /* off */ 2: or %g1, AUX_MBO, %g1 /* Must Be Ones */ stb %g1, [%o4] /* write aux i/o register */ retl wrpr %g0, %o2, %pstate /* enable interrupt */ SET_SIZE(set_auxioreg) #endif /* lint */ /* * Flush all windows to memory, except for the one we entered in. * We do this by doing NWINDOW-2 saves then the same number of restores. * This leaves the WIM immediately before window entered in. * This is used for context switching. */ #if defined(lint) void flush_windows(void) {} #else /* lint */ ENTRY_NP(flush_windows) retl flushw SET_SIZE(flush_windows) #endif /* lint */ #if defined(lint) void debug_flush_windows(void) {} #else /* lint */ ENTRY_NP(debug_flush_windows) set nwindows, %g1 ld [%g1], %g1 mov %g1, %g2 1: save %sp, -WINDOWSIZE, %sp brnz %g2, 1b dec %g2 mov %g1, %g2 2: restore brnz %g2, 2b dec %g2 retl nop SET_SIZE(debug_flush_windows) #endif /* lint */ /* * flush user windows to memory. */ #if defined(lint) void flush_user_windows(void) {} #else /* lint */ ENTRY_NP(flush_user_windows) rdpr %otherwin, %g1 brz %g1, 3f clr %g2 1: save %sp, -WINDOWSIZE, %sp rdpr %otherwin, %g1 brnz %g1, 1b add %g2, 1, %g2 2: sub %g2, 1, %g2 ! restore back to orig window brnz %g2, 2b restore 3: retl nop SET_SIZE(flush_user_windows) #endif /* lint */ /* * Throw out any user windows in the register file. * Used by setregs (exec) to clean out old user. * Used by sigcleanup to remove extraneous windows when returning from a * signal. */ #if defined(lint) void trash_user_windows(void) {} #else /* lint */ ENTRY_NP(trash_user_windows) rdpr %otherwin, %g1 brz %g1, 3f ! no user windows? ldn [THREAD_REG + T_STACK], %g5 ! ! There are old user windows in the register file. We disable ints ! and increment cansave so that we don't overflow on these windows. ! Also, this sets up a nice underflow when first returning to the ! new user. ! rdpr %pstate, %g2 wrpr %g2, PSTATE_IE, %pstate rdpr %cansave, %g3 rdpr %otherwin, %g1 ! re-read in case of interrupt add %g3, %g1, %g3 wrpr %g0, 0, %otherwin wrpr %g0, %g3, %cansave wrpr %g0, %g2, %pstate 3: retl clr [%g5 + MPCB_WBCNT] ! zero window buffer cnt SET_SIZE(trash_user_windows) #endif /* lint */ /* * Setup g7 via the CPU data structure. */ #if defined(lint) struct scb * set_tbr(struct scb *s) { return (s); } #else /* lint */ ENTRY_NP(set_tbr) retl ta 72 ! no tbr, stop simulation SET_SIZE(set_tbr) #endif /* lint */ #if defined(lint) /* * These need to be defined somewhere to lint and there is no "hicore.s"... */ char etext[1], end[1]; #endif /* lint*/ #if defined (lint) /* ARGSUSED */ void ptl1_panic(u_int reason) {} #else /* lint */ #define PTL1_SAVE_WINDOW(RP) \ stxa %l0, [RP + RW64_LOCAL + (0 * RW64_LOCAL_INCR)] %asi; \ stxa %l1, [RP + RW64_LOCAL + (1 * RW64_LOCAL_INCR)] %asi; \ stxa %l2, [RP + RW64_LOCAL + (2 * RW64_LOCAL_INCR)] %asi; \ stxa %l3, [RP + RW64_LOCAL + (3 * RW64_LOCAL_INCR)] %asi; \ stxa %l4, [RP + RW64_LOCAL + (4 * RW64_LOCAL_INCR)] %asi; \ stxa %l5, [RP + RW64_LOCAL + (5 * RW64_LOCAL_INCR)] %asi; \ stxa %l6, [RP + RW64_LOCAL + (6 * RW64_LOCAL_INCR)] %asi; \ stxa %l7, [RP + RW64_LOCAL + (7 * RW64_LOCAL_INCR)] %asi; \ stxa %i0, [RP + RW64_IN + (0 * RW64_IN_INCR)] %asi; \ stxa %i1, [RP + RW64_IN + (1 * RW64_IN_INCR)] %asi; \ stxa %i2, [RP + RW64_IN + (2 * RW64_IN_INCR)] %asi; \ stxa %i3, [RP + RW64_IN + (3 * RW64_IN_INCR)] %asi; \ stxa %i4, [RP + RW64_IN + (4 * RW64_IN_INCR)] %asi; \ stxa %i5, [RP + RW64_IN + (5 * RW64_IN_INCR)] %asi; \ stxa %i6, [RP + RW64_IN + (6 * RW64_IN_INCR)] %asi; \ stxa %i7, [RP + RW64_IN + (7 * RW64_IN_INCR)] %asi #define PTL1_NEXT_WINDOW(scr) \ add scr, RWIN64SIZE, scr #define PTL1_RESET_RWINDOWS(scr) \ sethi %hi(nwin_minus_one), scr; \ ld [scr + %lo(nwin_minus_one)], scr; \ wrpr scr, %cleanwin; \ dec scr; \ wrpr scr, %cansave; \ wrpr %g0, %canrestore; \ wrpr %g0, %otherwin #define PTL1_DCACHE_LINE_SIZE 4 /* small enough for all CPUs */ /* * ptl1_panic is called when the kernel detects that it is in an invalid state * and the trap level is greater than 0. ptl1_panic is responsible to save the * current CPU state, to restore the CPU state to normal, and to call panic. * The CPU state must be saved reliably without causing traps. ptl1_panic saves * it in the ptl1_state structure, which is a member of the machcpu structure. * In order to access the ptl1_state structure without causing traps, physical * addresses are used so that we can avoid MMU miss traps. The restriction of * physical memory accesses is that the ptl1_state structure must be on a single * physical page. This is because (1) a single physical address for each * ptl1_state structure is needed and (2) it simplifies physical address * calculation for each member of the structure. * ptl1_panic is a likely spot for stack overflows to wind up; thus, the current * stack may not be usable. In order to call panic reliably in such a state, * each CPU needs a dedicated ptl1 panic stack. * CPU_ALLOC_SIZE, which is defined to be MMU_PAGESIZE, is used to allocate the * cpu structure and a ptl1 panic stack. They are put together on the same page * for memory space efficiency. The low address part is used for the cpu * structure, and the high address part is for a ptl1 panic stack. * The cpu_pa array holds the physical addresses of the allocated cpu structures, * as the cpu array holds their virtual addresses. * * %g1 reason to be called * %g2 broken * %g3 broken */ ENTRY_NP(ptl1_panic) ! ! flush D$ first, so that stale data will not be accessed later. ! Data written via ASI_MEM bypasses D$. If D$ contains data at the same ! address, where data was written via ASI_MEM, a load from that address ! using a virtual address and the default ASI still takes the old data. ! Flushing D$ erases old data in D$, so that it will not be loaded. ! Since we can afford only 2 registers (%g2 and %g3) for this job, we ! flush entire D$. ! For FJ OPL processors (IMPL values < SPITFIRE_IMPL), DC flushing ! is not needed. ! GET_CPU_IMPL(%g2) cmp %g2, SPITFIRE_IMPL blt,pn %icc, 1f ! Skip flushing for OPL processors nop sethi %hi(dcache_size), %g2 ld [%g2 + %lo(dcache_size)], %g2 sethi %hi(dcache_linesize), %g3 ld [%g3 + %lo(dcache_linesize)], %g3 sub %g2, %g3, %g2 0: stxa %g0, [%g2] ASI_DC_TAG membar #Sync brnz,pt %g2, 0b sub %g2, %g3, %g2 1: ! ! increment the entry counter. ! save CPU state if this is the first entry. ! CPU_PADDR(%g2, %g3); add %g2, CPU_PTL1, %g2 ! pstate = &CPU->mcpu.ptl1_state wr %g0, ASI_MEM, %asi ! physical address access ! ! pstate->ptl1_entry_count++ ! lduwa [%g2 + PTL1_ENTRY_COUNT] %asi, %g3 add %g3, 1, %g3 stuwa %g3, [%g2 + PTL1_ENTRY_COUNT] %asi ! ! CPU state saving is skipped from the 2nd entry to ptl1_panic since we ! do not want to clobber the state from the original failure. panic() ! is responsible for handling multiple or recursive panics. ! cmp %g3, 2 ! if (ptl1_entry_count >= 2) bge,pn %icc, state_saved ! goto state_saved add %g2, PTL1_REGS, %g3 ! %g3 = &pstate->ptl1_regs[0] ! ! save CPU state ! save_cpu_state: ! save current global registers ! so that all them become available for use ! stxa %g1, [%g3 + PTL1_G1] %asi stxa %g2, [%g3 + PTL1_G2] %asi stxa %g3, [%g3 + PTL1_G3] %asi stxa %g4, [%g3 + PTL1_G4] %asi stxa %g5, [%g3 + PTL1_G5] %asi stxa %g6, [%g3 + PTL1_G6] %asi stxa %g7, [%g3 + PTL1_G7] %asi ! ! %tl, %tt, %tstate, %tpc, %tnpc for each TL ! rdpr %tl, %g1 brz %g1, 1f ! if(trap_level == 0) -------+ add %g3, PTL1_TRAP_REGS, %g4 ! %g4 = &ptl1_trap_regs[0]; ! 0: ! -----------<----------+ ! stwa %g1, [%g4 + PTL1_TL] %asi ! ! rdpr %tt, %g5 ! ! stwa %g5, [%g4 + PTL1_TT] %asi ! ! rdpr %tstate, %g5 ! ! stxa %g5, [%g4 + PTL1_TSTATE] %asi ! ! rdpr %tpc, %g5 ! ! stxa %g5, [%g4 + PTL1_TPC] %asi ! ! rdpr %tnpc, %g5 ! ! stxa %g5, [%g4 + PTL1_TNPC] %asi ! ! add %g4, PTL1_TRAP_REGS_INCR, %g4 ! ! deccc %g1 ! ! bnz,a,pt %icc, 0b ! if(trap_level != 0) --+ ! wrpr %g1, %tl ! 1: ! ----------<----------------+ ! ! %pstate, %pil, SOFTINT, (S)TICK ! Pending interrupts is also cleared in order to avoid a recursive call ! to ptl1_panic in case the interrupt handler causes a panic. ! rdpr %pil, %g1 stba %g1, [%g3 + PTL1_PIL] %asi rdpr %pstate, %g1 stha %g1, [%g3 + PTL1_PSTATE] %asi rd SOFTINT, %g1 sta %g1, [%g3 + PTL1_SOFTINT] %asi wr %g1, CLEAR_SOFTINT sethi %hi(traptrace_use_stick), %g1 ld [%g1 + %lo(traptrace_use_stick)], %g1 brz,a,pn %g1, 2f rdpr %tick, %g1 rd STICK, %g1 2: stxa %g1, [%g3 + PTL1_TICK] %asi ! ! MMU registers because ptl1_panic may be called from ! the MMU trap handlers. ! mov MMU_SFAR, %g1 ldxa [%g1]ASI_DMMU, %g4 stxa %g4, [%g3 + PTL1_DMMU_SFAR]%asi mov MMU_SFSR, %g1 ldxa [%g1]ASI_DMMU, %g4 stxa %g4, [%g3 + PTL1_DMMU_SFSR]%asi ldxa [%g1]ASI_IMMU, %g4 stxa %g4, [%g3 + PTL1_IMMU_SFSR]%asi mov MMU_TAG_ACCESS, %g1 ldxa [%g1]ASI_DMMU, %g4 stxa %g4, [%g3 + PTL1_DMMU_TAG_ACCESS]%asi ldxa [%g1]ASI_IMMU, %g4 stxa %g4, [%g3 + PTL1_IMMU_TAG_ACCESS]%asi ! ! Save register window state and register windows. ! rdpr %cwp, %g1 stba %g1, [%g3 + PTL1_CWP] %asi rdpr %wstate, %g1 stba %g1, [%g3 + PTL1_WSTATE] %asi rdpr %otherwin, %g1 stba %g1, [%g3 + PTL1_OTHERWIN] %asi rdpr %cleanwin, %g1 stba %g1, [%g3 + PTL1_CLEANWIN] %asi rdpr %cansave, %g1 stba %g1, [%g3 + PTL1_CANSAVE] %asi rdpr %canrestore, %g1 stba %g1, [%g3 + PTL1_CANRESTORE] %asi PTL1_RESET_RWINDOWS(%g1) clr %g1 wrpr %g1, %cwp add %g3, PTL1_RWINDOW, %g4 ! %g4 = &ptl1_rwindow[0]; 3: PTL1_SAVE_WINDOW(%g4) ! <-------------+ inc %g1 ! cmp %g1, MAXWIN ! bgeu,pn %icc, 5f ! wrpr %g1, %cwp ! rdpr %cwp, %g2 ! cmp %g1, %g2 ! saturation check be,pt %icc, 3b ! PTL1_NEXT_WINDOW(%g4) ! ------+ 5: ! ! most crucial CPU state was saved. ! Proceed to go back to TL = 0. ! state_saved: wrpr %g0, 1, %tl wrpr %g0, PIL_MAX, %pil ! PTL1_RESET_RWINDOWS(%g1) wrpr %g0, %cwp wrpr %g0, %cleanwin wrpr %g0, WSTATE_KERN, %wstate ! ! Set pcontext to run kernel. ! ! For OPL, load kcontexreg instead of clearing primary ! context register. This is to avoid changing nucleus page ! size bits after boot initialization. ! #ifdef _OPL sethi %hi(kcontextreg), %g4 ldx [%g4 + %lo(kcontextreg)], %g4 #endif /* _OPL */ set DEMAP_ALL_TYPE, %g1 sethi %hi(FLUSH_ADDR), %g3 set MMU_PCONTEXT, %g2 stxa %g0, [%g1]ASI_DTLB_DEMAP stxa %g0, [%g1]ASI_ITLB_DEMAP #ifdef _OPL stxa %g4, [%g2]ASI_MMU_CTX #else /* _OPL */ stxa %g0, [%g2]ASI_MMU_CTX #endif /* _OPL */ flush %g3 rdpr %cwp, %g1 set TSTATE_KERN, %g3 wrpr %g3, %g1, %tstate set ptl1_panic_tl0, %g3 wrpr %g0, %g3, %tnpc done ! go to -->-+ TL:1 ! ptl1_panic_tl0: ! ----<-----+ TL:0 CPU_ADDR(%l0, %l1) ! %l0 = cpu[cpuid] add %l0, CPU_PTL1, %l1 ! %l1 = &CPU->mcpu.ptl1_state ! ! prepare to call panic() ! ldn [%l0 + CPU_THREAD], THREAD_REG ! restore %g7 ldn [%l1 + PTL1_STKTOP], %l2 ! %sp = ptl1_stktop sub %l2, SA(MINFRAME) + STACK_BIAS, %sp clr %fp ! no frame below this window clr %i7 ! ! enable limited interrupts ! wrpr %g0, CLOCK_LEVEL, %pil wrpr %g0, PSTATE_KERN, %pstate ! ba,pt %xcc, ptl1_panic_handler mov %l1, %o0 /*NOTREACHED*/ SET_SIZE(ptl1_panic) #endif /* lint */ #ifdef PTL1_PANIC_DEBUG #if defined (lint) /* * ptl1_recurse() calls itself a number of times to either set up a known * stack or to cause a kernel stack overflow. It decrements the arguments * on each recursion. * It's called by #ifdef PTL1_PANIC_DEBUG code in startup.c to set the * registers to a known state to facilitate debugging. */ /* ARGSUSED */ void ptl1_recurse(int count_threshold, int trap_threshold) {} #else /* lint */ ENTRY_NP(ptl1_recurse) save %sp, -SA(MINFRAME), %sp set ptl1_recurse_call, %o7 cmp %o7, %i7 ! if ptl1_recurse is called be,pt %icc, 0f ! by itself, then skip nop ! register initialization /* * Initialize Out Registers to Known Values */ set 0x01000, %l0 ! %i0 is the ... ! recursion_depth_count sub %i0, 1, %o0; sub %i1, 1, %o1; add %l0, %o0, %o2; add %l0, %o2, %o3; add %l0, %o3, %o4; add %l0, %o4, %o5; ba,a 1f nop 0: /* Outs = Ins - 1 */ sub %i0, 1, %o0; sub %i1, 1, %o1; sub %i2, 1, %o2; sub %i3, 1, %o3; sub %i4, 1, %o4; sub %i5, 1, %o5; /* Locals = Ins + 1 */ 1: add %i0, 1, %l0; add %i1, 1, %l1; add %i2, 1, %l2; add %i3, 1, %l3; add %i4, 1, %l4; add %i5, 1, %l5; set 0x0100000, %g5 add %g5, %g0, %g1 add %g5, %g1, %g2 add %g5, %g2, %g3 add %g5, %g3, %g4 add %g5, %g4, %g5 brz,pn %i1, ptl1_recurse_trap ! if trpp_count == 0) { nop ! trap to ptl1_panic ! brz,pn %i0, ptl1_recure_exit ! if(depth_count == 0) { nop ! skip recursive call ! } ptl1_recurse_call: call ptl1_recurse nop ptl1_recure_exit: ret restore ptl1_recurse_trap: ta PTL1_DEBUG_TRAP; ! Trap Always to ptl1_panic() nop ! NOTREACHED SET_SIZE(ptl1_recurse) #endif /* lint */ #if defined (lint) /* ARGSUSED */ void ptl1_panic_xt(int arg1, int arg2) {} #else /* lint */ /* * Asm function to handle a cross trap to call ptl1_panic() */ ENTRY_NP(ptl1_panic_xt) ba ptl1_panic mov PTL1_BAD_DEBUG, %g1 SET_SIZE(ptl1_panic_xt) #endif /* lint */ #endif /* PTL1_PANIC_DEBUG */ #ifdef TRAPTRACE #if defined (lint) void trace_ptr_panic(void) { } #else /* lint */ ENTRY_NP(trace_ptr_panic) ! ! freeze the trap trace to disable the assertions. Otherwise, ! ptl1_panic is likely to be repeatedly called from there. ! %g2 and %g3 are used as scratch registers in ptl1_panic. ! mov 1, %g3 sethi %hi(trap_freeze), %g2 st %g3, [%g2 + %lo(trap_freeze)] ! ! %g1 contains the %pc address where an assertion was failed. ! save it in trap_freeze_pc for a debugging hint if there is ! no value saved in it. ! set trap_freeze_pc, %g2 casn [%g2], %g0, %g1 ba ptl1_panic mov PTL1_BAD_TRACE_PTR, %g1 SET_SIZE(trace_ptr_panic) #endif /* lint */ #endif /* TRAPTRACE */ #if defined (lint) /* * set_kcontextreg() sets PCONTEXT to kctx * if PCONTEXT==kctx, do nothing * if N_pgsz0|N_pgsz1 differ, do demap all first */ /* ARGSUSED */ void set_kcontextreg() { } #else /* lint */ ENTRY_NP(set_kcontextreg) ! SET_KCONTEXTREG(reg0, reg1, reg2, reg3, reg4, label1, label2, label3) SET_KCONTEXTREG(%o0, %o1, %o2, %o3, %o4, l1, l2, l3) retl nop SET_SIZE(set_kcontextreg) #endif /* lint */ /* * The interface for a 32-bit client program that takes over the TBA * calling the 64-bit romvec OBP. */ #if defined(lint) /* ARGSUSED */ int client_handler(void *cif_handler, void *arg_array) { return 0; } #else /* lint */ ENTRY(client_handler) save %sp, -SA64(MINFRAME64), %sp ! 32 bit frame, 64 bit sized sethi %hi(tba_taken_over), %l2 ld [%l2+%lo(tba_taken_over)], %l3 brz %l3, 1f ! is the tba_taken_over = 1 ? rdpr %wstate, %l5 ! save %wstate andn %l5, WSTATE_MASK, %l6 wrpr %l6, WSTATE_KMIX, %wstate ! ! switch to PCONTEXT=0 ! #ifndef _OPL mov MMU_PCONTEXT, %o2 ldxa [%o2]ASI_DMMU, %o2 srlx %o2, CTXREG_NEXT_SHIFT, %o2 brz,pt %o2, 1f ! nucleus pgsz is 0, no problem nop rdpr %pstate, %l4 ! disable interrupts andn %l4, PSTATE_IE, %o2 wrpr %g0, %o2, %pstate mov DEMAP_ALL_TYPE, %o2 ! set PCONTEXT=0 stxa %g0, [%o2]ASI_DTLB_DEMAP stxa %g0, [%o2]ASI_ITLB_DEMAP mov MMU_PCONTEXT, %o2 stxa %g0, [%o2]ASI_DMMU membar #Sync sethi %hi(FLUSH_ADDR), %o2 flush %o2 ! flush required by immu wrpr %g0, %l4, %pstate ! restore interrupt state #endif /* _OPL */ 1: mov %i1, %o0 rdpr %pstate, %l4 ! Get the present pstate value andn %l4, PSTATE_AM, %l6 wrpr %l6, 0, %pstate ! Set PSTATE_AM = 0 jmpl %i0, %o7 ! Call cif handler nop wrpr %l4, 0, %pstate ! restore pstate brz %l3, 1f ! is the tba_taken_over = 1 nop wrpr %g0, %l5, %wstate ! restore wstate ! ! switch to PCONTEXT=kcontexreg ! #ifndef _OPL sethi %hi(kcontextreg), %o3 ldx [%o3 + %lo(kcontextreg)], %o3 brz %o3, 1f nop rdpr %pstate, %l4 ! disable interrupts andn %l4, PSTATE_IE, %o2 wrpr %g0, %o2, %pstate mov DEMAP_ALL_TYPE, %o2 stxa %g0, [%o2]ASI_DTLB_DEMAP stxa %g0, [%o2]ASI_ITLB_DEMAP mov MMU_PCONTEXT, %o2 stxa %o3, [%o2]ASI_DMMU membar #Sync sethi %hi(FLUSH_ADDR), %o2 flush %o2 ! flush required by immu wrpr %g0, %l4, %pstate ! restore interrupt state #endif /* _OPL */ 1: ret ! Return result ... restore %o0, %g0, %o0 ! delay; result in %o0 SET_SIZE(client_handler) #endif /* lint */