mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-27 06:34:11 +08:00
6cc0c16d82
Implement the bulk of interrupt return logic in C. The asm return code must handle a few cases: restoring full GPRs, and emulating stack store. The stack store emulation is significantly simplfied, rather than creating a new return frame and switching to that before performing the store, it uses the PACA to keep a scratch register around to perform the store. The asm return code is moved into 64e for now. The new logic has made allowance for 64e, but I don't have a full environment that works well to test it, and even booting in emulated qemu is not great for stress testing. 64e shouldn't be too far off working with this, given a bit more testing and auditing of the logic. This is slightly faster on a POWER9 (page fault speed increases about 1.1%), probably due to reduced mtmsrd. mpe: Includes fixes from Nick for _TIF_EMULATE_STACK_STORE handling (including the fast_interrupt_return path), to remove trace_hardirqs_on(), and fixes the interrupt-return part of the MSR_VSX restore bug caught by tm-unavailable selftest. mpe: Incorporate fix from Nick: The return-to-kernel path has to replay any soft-pending interrupts if it is returning to a context that had interrupts soft-enabled. It has to do this carefully and avoid plain enabling interrupts if this is an irq context, which can cause multiple nesting of interrupts on the stack, and other unexpected issues. The code which avoided this case got the soft-mask state wrong, and marked interrupts as enabled before going around again to retry. This seems to be mostly harmless except when PREEMPT=y, this calls preempt_schedule_irq with irqs apparently enabled and runs into a BUG in kernel/sched/core.c Signed-off-by: Nicholas Piggin <npiggin@gmail.com> Signed-off-by: Michal Suchanek <msuchanek@suse.de> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://lore.kernel.org/r/20200225173541.1549955-29-npiggin@gmail.com
1891 lines
50 KiB
ArmAsm
1891 lines
50 KiB
ArmAsm
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Boot code and exception vectors for Book3E processors
|
|
*
|
|
* Copyright (C) 2007 Ben. Herrenschmidt (benh@kernel.crashing.org), IBM Corp.
|
|
*/
|
|
|
|
#include <linux/threads.h>
|
|
#include <asm/reg.h>
|
|
#include <asm/page.h>
|
|
#include <asm/ppc_asm.h>
|
|
#include <asm/asm-offsets.h>
|
|
#include <asm/cputable.h>
|
|
#include <asm/setup.h>
|
|
#include <asm/thread_info.h>
|
|
#include <asm/reg_a2.h>
|
|
#include <asm/exception-64e.h>
|
|
#include <asm/bug.h>
|
|
#include <asm/irqflags.h>
|
|
#include <asm/ptrace.h>
|
|
#include <asm/ppc-opcode.h>
|
|
#include <asm/mmu.h>
|
|
#include <asm/hw_irq.h>
|
|
#include <asm/kvm_asm.h>
|
|
#include <asm/kvm_booke_hv_asm.h>
|
|
#include <asm/feature-fixups.h>
|
|
#include <asm/context_tracking.h>
|
|
|
|
/* XXX This will ultimately add space for a special exception save
|
|
* structure used to save things like SRR0/SRR1, SPRGs, MAS, etc...
|
|
* when taking special interrupts. For now we don't support that,
|
|
* special interrupts from within a non-standard level will probably
|
|
* blow you up
|
|
*/
|
|
#define SPECIAL_EXC_SRR0 0
|
|
#define SPECIAL_EXC_SRR1 1
|
|
#define SPECIAL_EXC_SPRG_GEN 2
|
|
#define SPECIAL_EXC_SPRG_TLB 3
|
|
#define SPECIAL_EXC_MAS0 4
|
|
#define SPECIAL_EXC_MAS1 5
|
|
#define SPECIAL_EXC_MAS2 6
|
|
#define SPECIAL_EXC_MAS3 7
|
|
#define SPECIAL_EXC_MAS6 8
|
|
#define SPECIAL_EXC_MAS7 9
|
|
#define SPECIAL_EXC_MAS5 10 /* E.HV only */
|
|
#define SPECIAL_EXC_MAS8 11 /* E.HV only */
|
|
#define SPECIAL_EXC_IRQHAPPENED 12
|
|
#define SPECIAL_EXC_DEAR 13
|
|
#define SPECIAL_EXC_ESR 14
|
|
#define SPECIAL_EXC_SOFTE 15
|
|
#define SPECIAL_EXC_CSRR0 16
|
|
#define SPECIAL_EXC_CSRR1 17
|
|
/* must be even to keep 16-byte stack alignment */
|
|
#define SPECIAL_EXC_END 18
|
|
|
|
#define SPECIAL_EXC_FRAME_SIZE (INT_FRAME_SIZE + SPECIAL_EXC_END * 8)
|
|
#define SPECIAL_EXC_FRAME_OFFS (INT_FRAME_SIZE - 288)
|
|
|
|
#define SPECIAL_EXC_STORE(reg, name) \
|
|
std reg, (SPECIAL_EXC_##name * 8 + SPECIAL_EXC_FRAME_OFFS)(r1)
|
|
|
|
#define SPECIAL_EXC_LOAD(reg, name) \
|
|
ld reg, (SPECIAL_EXC_##name * 8 + SPECIAL_EXC_FRAME_OFFS)(r1)
|
|
|
|
special_reg_save:
|
|
lbz r9,PACAIRQHAPPENED(r13)
|
|
RECONCILE_IRQ_STATE(r3,r4)
|
|
|
|
/*
|
|
* We only need (or have stack space) to save this stuff if
|
|
* we interrupted the kernel.
|
|
*/
|
|
ld r3,_MSR(r1)
|
|
andi. r3,r3,MSR_PR
|
|
bnelr
|
|
|
|
/*
|
|
* Advance to the next TLB exception frame for handler
|
|
* types that don't do it automatically.
|
|
*/
|
|
LOAD_REG_ADDR(r11,extlb_level_exc)
|
|
lwz r12,0(r11)
|
|
mfspr r10,SPRN_SPRG_TLB_EXFRAME
|
|
add r10,r10,r12
|
|
mtspr SPRN_SPRG_TLB_EXFRAME,r10
|
|
|
|
/*
|
|
* Save registers needed to allow nesting of certain exceptions
|
|
* (such as TLB misses) inside special exception levels
|
|
*/
|
|
mfspr r10,SPRN_SRR0
|
|
SPECIAL_EXC_STORE(r10,SRR0)
|
|
mfspr r10,SPRN_SRR1
|
|
SPECIAL_EXC_STORE(r10,SRR1)
|
|
mfspr r10,SPRN_SPRG_GEN_SCRATCH
|
|
SPECIAL_EXC_STORE(r10,SPRG_GEN)
|
|
mfspr r10,SPRN_SPRG_TLB_SCRATCH
|
|
SPECIAL_EXC_STORE(r10,SPRG_TLB)
|
|
mfspr r10,SPRN_MAS0
|
|
SPECIAL_EXC_STORE(r10,MAS0)
|
|
mfspr r10,SPRN_MAS1
|
|
SPECIAL_EXC_STORE(r10,MAS1)
|
|
mfspr r10,SPRN_MAS2
|
|
SPECIAL_EXC_STORE(r10,MAS2)
|
|
mfspr r10,SPRN_MAS3
|
|
SPECIAL_EXC_STORE(r10,MAS3)
|
|
mfspr r10,SPRN_MAS6
|
|
SPECIAL_EXC_STORE(r10,MAS6)
|
|
mfspr r10,SPRN_MAS7
|
|
SPECIAL_EXC_STORE(r10,MAS7)
|
|
BEGIN_FTR_SECTION
|
|
mfspr r10,SPRN_MAS5
|
|
SPECIAL_EXC_STORE(r10,MAS5)
|
|
mfspr r10,SPRN_MAS8
|
|
SPECIAL_EXC_STORE(r10,MAS8)
|
|
|
|
/* MAS5/8 could have inappropriate values if we interrupted KVM code */
|
|
li r10,0
|
|
mtspr SPRN_MAS5,r10
|
|
mtspr SPRN_MAS8,r10
|
|
END_FTR_SECTION_IFSET(CPU_FTR_EMB_HV)
|
|
SPECIAL_EXC_STORE(r9,IRQHAPPENED)
|
|
|
|
mfspr r10,SPRN_DEAR
|
|
SPECIAL_EXC_STORE(r10,DEAR)
|
|
mfspr r10,SPRN_ESR
|
|
SPECIAL_EXC_STORE(r10,ESR)
|
|
|
|
lbz r10,PACAIRQSOFTMASK(r13)
|
|
SPECIAL_EXC_STORE(r10,SOFTE)
|
|
ld r10,_NIP(r1)
|
|
SPECIAL_EXC_STORE(r10,CSRR0)
|
|
ld r10,_MSR(r1)
|
|
SPECIAL_EXC_STORE(r10,CSRR1)
|
|
|
|
blr
|
|
|
|
ret_from_level_except:
|
|
ld r3,_MSR(r1)
|
|
andi. r3,r3,MSR_PR
|
|
beq 1f
|
|
b ret_from_except
|
|
1:
|
|
|
|
LOAD_REG_ADDR(r11,extlb_level_exc)
|
|
lwz r12,0(r11)
|
|
mfspr r10,SPRN_SPRG_TLB_EXFRAME
|
|
sub r10,r10,r12
|
|
mtspr SPRN_SPRG_TLB_EXFRAME,r10
|
|
|
|
/*
|
|
* It's possible that the special level exception interrupted a
|
|
* TLB miss handler, and inserted the same entry that the
|
|
* interrupted handler was about to insert. On CPUs without TLB
|
|
* write conditional, this can result in a duplicate TLB entry.
|
|
* Wipe all non-bolted entries to be safe.
|
|
*
|
|
* Note that this doesn't protect against any TLB misses
|
|
* we may take accessing the stack from here to the end of
|
|
* the special level exception. It's not clear how we can
|
|
* reasonably protect against that, but only CPUs with
|
|
* neither TLB write conditional nor bolted kernel memory
|
|
* are affected. Do any such CPUs even exist?
|
|
*/
|
|
PPC_TLBILX_ALL(0,R0)
|
|
|
|
REST_NVGPRS(r1)
|
|
|
|
SPECIAL_EXC_LOAD(r10,SRR0)
|
|
mtspr SPRN_SRR0,r10
|
|
SPECIAL_EXC_LOAD(r10,SRR1)
|
|
mtspr SPRN_SRR1,r10
|
|
SPECIAL_EXC_LOAD(r10,SPRG_GEN)
|
|
mtspr SPRN_SPRG_GEN_SCRATCH,r10
|
|
SPECIAL_EXC_LOAD(r10,SPRG_TLB)
|
|
mtspr SPRN_SPRG_TLB_SCRATCH,r10
|
|
SPECIAL_EXC_LOAD(r10,MAS0)
|
|
mtspr SPRN_MAS0,r10
|
|
SPECIAL_EXC_LOAD(r10,MAS1)
|
|
mtspr SPRN_MAS1,r10
|
|
SPECIAL_EXC_LOAD(r10,MAS2)
|
|
mtspr SPRN_MAS2,r10
|
|
SPECIAL_EXC_LOAD(r10,MAS3)
|
|
mtspr SPRN_MAS3,r10
|
|
SPECIAL_EXC_LOAD(r10,MAS6)
|
|
mtspr SPRN_MAS6,r10
|
|
SPECIAL_EXC_LOAD(r10,MAS7)
|
|
mtspr SPRN_MAS7,r10
|
|
BEGIN_FTR_SECTION
|
|
SPECIAL_EXC_LOAD(r10,MAS5)
|
|
mtspr SPRN_MAS5,r10
|
|
SPECIAL_EXC_LOAD(r10,MAS8)
|
|
mtspr SPRN_MAS8,r10
|
|
END_FTR_SECTION_IFSET(CPU_FTR_EMB_HV)
|
|
|
|
lbz r6,PACAIRQSOFTMASK(r13)
|
|
ld r5,SOFTE(r1)
|
|
|
|
/* Interrupts had better not already be enabled... */
|
|
tweqi r6,IRQS_ENABLED
|
|
|
|
andi. r6,r5,IRQS_DISABLED
|
|
bne 1f
|
|
|
|
TRACE_ENABLE_INTS
|
|
stb r5,PACAIRQSOFTMASK(r13)
|
|
1:
|
|
/*
|
|
* Restore PACAIRQHAPPENED rather than setting it based on
|
|
* the return MSR[EE], since we could have interrupted
|
|
* __check_irq_replay() or other inconsistent transitory
|
|
* states that must remain that way.
|
|
*/
|
|
SPECIAL_EXC_LOAD(r10,IRQHAPPENED)
|
|
stb r10,PACAIRQHAPPENED(r13)
|
|
|
|
SPECIAL_EXC_LOAD(r10,DEAR)
|
|
mtspr SPRN_DEAR,r10
|
|
SPECIAL_EXC_LOAD(r10,ESR)
|
|
mtspr SPRN_ESR,r10
|
|
|
|
stdcx. r0,0,r1 /* to clear the reservation */
|
|
|
|
REST_4GPRS(2, r1)
|
|
REST_4GPRS(6, r1)
|
|
|
|
ld r10,_CTR(r1)
|
|
ld r11,_XER(r1)
|
|
mtctr r10
|
|
mtxer r11
|
|
|
|
blr
|
|
|
|
.macro ret_from_level srr0 srr1 paca_ex scratch
|
|
bl ret_from_level_except
|
|
|
|
ld r10,_LINK(r1)
|
|
ld r11,_CCR(r1)
|
|
ld r0,GPR13(r1)
|
|
mtlr r10
|
|
mtcr r11
|
|
|
|
ld r10,GPR10(r1)
|
|
ld r11,GPR11(r1)
|
|
ld r12,GPR12(r1)
|
|
mtspr \scratch,r0
|
|
|
|
std r10,\paca_ex+EX_R10(r13);
|
|
std r11,\paca_ex+EX_R11(r13);
|
|
ld r10,_NIP(r1)
|
|
ld r11,_MSR(r1)
|
|
ld r0,GPR0(r1)
|
|
ld r1,GPR1(r1)
|
|
mtspr \srr0,r10
|
|
mtspr \srr1,r11
|
|
ld r10,\paca_ex+EX_R10(r13)
|
|
ld r11,\paca_ex+EX_R11(r13)
|
|
mfspr r13,\scratch
|
|
.endm
|
|
|
|
ret_from_crit_except:
|
|
ret_from_level SPRN_CSRR0 SPRN_CSRR1 PACA_EXCRIT SPRN_SPRG_CRIT_SCRATCH
|
|
rfci
|
|
|
|
ret_from_mc_except:
|
|
ret_from_level SPRN_MCSRR0 SPRN_MCSRR1 PACA_EXMC SPRN_SPRG_MC_SCRATCH
|
|
rfmci
|
|
|
|
/* Exception prolog code for all exceptions */
|
|
#define EXCEPTION_PROLOG(n, intnum, type, addition) \
|
|
mtspr SPRN_SPRG_##type##_SCRATCH,r13; /* get spare registers */ \
|
|
mfspr r13,SPRN_SPRG_PACA; /* get PACA */ \
|
|
std r10,PACA_EX##type+EX_R10(r13); \
|
|
std r11,PACA_EX##type+EX_R11(r13); \
|
|
mfcr r10; /* save CR */ \
|
|
mfspr r11,SPRN_##type##_SRR1;/* what are we coming from */ \
|
|
DO_KVM intnum,SPRN_##type##_SRR1; /* KVM hook */ \
|
|
stw r10,PACA_EX##type+EX_CR(r13); /* save old CR in the PACA */ \
|
|
addition; /* additional code for that exc. */ \
|
|
std r1,PACA_EX##type+EX_R1(r13); /* save old r1 in the PACA */ \
|
|
type##_SET_KSTACK; /* get special stack if necessary */\
|
|
andi. r10,r11,MSR_PR; /* save stack pointer */ \
|
|
beq 1f; /* branch around if supervisor */ \
|
|
ld r1,PACAKSAVE(r13); /* get kernel stack coming from usr */\
|
|
1: type##_BTB_FLUSH \
|
|
cmpdi cr1,r1,0; /* check if SP makes sense */ \
|
|
bge- cr1,exc_##n##_bad_stack;/* bad stack (TODO: out of line) */ \
|
|
mfspr r10,SPRN_##type##_SRR0; /* read SRR0 before touching stack */
|
|
|
|
/* Exception type-specific macros */
|
|
#define GEN_SET_KSTACK \
|
|
subi r1,r1,INT_FRAME_SIZE; /* alloc frame on kernel stack */
|
|
#define SPRN_GEN_SRR0 SPRN_SRR0
|
|
#define SPRN_GEN_SRR1 SPRN_SRR1
|
|
|
|
#define GDBELL_SET_KSTACK GEN_SET_KSTACK
|
|
#define SPRN_GDBELL_SRR0 SPRN_GSRR0
|
|
#define SPRN_GDBELL_SRR1 SPRN_GSRR1
|
|
|
|
#define CRIT_SET_KSTACK \
|
|
ld r1,PACA_CRIT_STACK(r13); \
|
|
subi r1,r1,SPECIAL_EXC_FRAME_SIZE
|
|
#define SPRN_CRIT_SRR0 SPRN_CSRR0
|
|
#define SPRN_CRIT_SRR1 SPRN_CSRR1
|
|
|
|
#define DBG_SET_KSTACK \
|
|
ld r1,PACA_DBG_STACK(r13); \
|
|
subi r1,r1,SPECIAL_EXC_FRAME_SIZE
|
|
#define SPRN_DBG_SRR0 SPRN_DSRR0
|
|
#define SPRN_DBG_SRR1 SPRN_DSRR1
|
|
|
|
#define MC_SET_KSTACK \
|
|
ld r1,PACA_MC_STACK(r13); \
|
|
subi r1,r1,SPECIAL_EXC_FRAME_SIZE
|
|
#define SPRN_MC_SRR0 SPRN_MCSRR0
|
|
#define SPRN_MC_SRR1 SPRN_MCSRR1
|
|
|
|
#ifdef CONFIG_PPC_FSL_BOOK3E
|
|
#define GEN_BTB_FLUSH \
|
|
START_BTB_FLUSH_SECTION \
|
|
beq 1f; \
|
|
BTB_FLUSH(r10) \
|
|
1: \
|
|
END_BTB_FLUSH_SECTION
|
|
|
|
#define CRIT_BTB_FLUSH \
|
|
START_BTB_FLUSH_SECTION \
|
|
BTB_FLUSH(r10) \
|
|
END_BTB_FLUSH_SECTION
|
|
|
|
#define DBG_BTB_FLUSH CRIT_BTB_FLUSH
|
|
#define MC_BTB_FLUSH CRIT_BTB_FLUSH
|
|
#define GDBELL_BTB_FLUSH GEN_BTB_FLUSH
|
|
#else
|
|
#define GEN_BTB_FLUSH
|
|
#define CRIT_BTB_FLUSH
|
|
#define DBG_BTB_FLUSH
|
|
#define MC_BTB_FLUSH
|
|
#define GDBELL_BTB_FLUSH
|
|
#endif
|
|
|
|
#define NORMAL_EXCEPTION_PROLOG(n, intnum, addition) \
|
|
EXCEPTION_PROLOG(n, intnum, GEN, addition##_GEN(n))
|
|
|
|
#define CRIT_EXCEPTION_PROLOG(n, intnum, addition) \
|
|
EXCEPTION_PROLOG(n, intnum, CRIT, addition##_CRIT(n))
|
|
|
|
#define DBG_EXCEPTION_PROLOG(n, intnum, addition) \
|
|
EXCEPTION_PROLOG(n, intnum, DBG, addition##_DBG(n))
|
|
|
|
#define MC_EXCEPTION_PROLOG(n, intnum, addition) \
|
|
EXCEPTION_PROLOG(n, intnum, MC, addition##_MC(n))
|
|
|
|
#define GDBELL_EXCEPTION_PROLOG(n, intnum, addition) \
|
|
EXCEPTION_PROLOG(n, intnum, GDBELL, addition##_GDBELL(n))
|
|
|
|
/* Variants of the "addition" argument for the prolog
|
|
*/
|
|
#define PROLOG_ADDITION_NONE_GEN(n)
|
|
#define PROLOG_ADDITION_NONE_GDBELL(n)
|
|
#define PROLOG_ADDITION_NONE_CRIT(n)
|
|
#define PROLOG_ADDITION_NONE_DBG(n)
|
|
#define PROLOG_ADDITION_NONE_MC(n)
|
|
|
|
#define PROLOG_ADDITION_MASKABLE_GEN(n) \
|
|
lbz r10,PACAIRQSOFTMASK(r13); /* are irqs soft-masked? */ \
|
|
andi. r10,r10,IRQS_DISABLED; /* yes -> go out of line */ \
|
|
bne masked_interrupt_book3e_##n
|
|
|
|
#define PROLOG_ADDITION_2REGS_GEN(n) \
|
|
std r14,PACA_EXGEN+EX_R14(r13); \
|
|
std r15,PACA_EXGEN+EX_R15(r13)
|
|
|
|
#define PROLOG_ADDITION_1REG_GEN(n) \
|
|
std r14,PACA_EXGEN+EX_R14(r13);
|
|
|
|
#define PROLOG_ADDITION_2REGS_CRIT(n) \
|
|
std r14,PACA_EXCRIT+EX_R14(r13); \
|
|
std r15,PACA_EXCRIT+EX_R15(r13)
|
|
|
|
#define PROLOG_ADDITION_2REGS_DBG(n) \
|
|
std r14,PACA_EXDBG+EX_R14(r13); \
|
|
std r15,PACA_EXDBG+EX_R15(r13)
|
|
|
|
#define PROLOG_ADDITION_2REGS_MC(n) \
|
|
std r14,PACA_EXMC+EX_R14(r13); \
|
|
std r15,PACA_EXMC+EX_R15(r13)
|
|
|
|
|
|
/* Core exception code for all exceptions except TLB misses. */
|
|
#define EXCEPTION_COMMON_LVL(n, scratch, excf) \
|
|
exc_##n##_common: \
|
|
std r0,GPR0(r1); /* save r0 in stackframe */ \
|
|
std r2,GPR2(r1); /* save r2 in stackframe */ \
|
|
SAVE_4GPRS(3, r1); /* save r3 - r6 in stackframe */ \
|
|
SAVE_2GPRS(7, r1); /* save r7, r8 in stackframe */ \
|
|
std r9,GPR9(r1); /* save r9 in stackframe */ \
|
|
std r10,_NIP(r1); /* save SRR0 to stackframe */ \
|
|
std r11,_MSR(r1); /* save SRR1 to stackframe */ \
|
|
beq 2f; /* if from kernel mode */ \
|
|
ACCOUNT_CPU_USER_ENTRY(r13,r10,r11);/* accounting (uses cr0+eq) */ \
|
|
2: ld r3,excf+EX_R10(r13); /* get back r10 */ \
|
|
ld r4,excf+EX_R11(r13); /* get back r11 */ \
|
|
mfspr r5,scratch; /* get back r13 */ \
|
|
std r12,GPR12(r1); /* save r12 in stackframe */ \
|
|
ld r2,PACATOC(r13); /* get kernel TOC into r2 */ \
|
|
mflr r6; /* save LR in stackframe */ \
|
|
mfctr r7; /* save CTR in stackframe */ \
|
|
mfspr r8,SPRN_XER; /* save XER in stackframe */ \
|
|
ld r9,excf+EX_R1(r13); /* load orig r1 back from PACA */ \
|
|
lwz r10,excf+EX_CR(r13); /* load orig CR back from PACA */ \
|
|
lbz r11,PACAIRQSOFTMASK(r13); /* get current IRQ softe */ \
|
|
ld r12,exception_marker@toc(r2); \
|
|
li r0,0; \
|
|
std r3,GPR10(r1); /* save r10 to stackframe */ \
|
|
std r4,GPR11(r1); /* save r11 to stackframe */ \
|
|
std r5,GPR13(r1); /* save it to stackframe */ \
|
|
std r6,_LINK(r1); \
|
|
std r7,_CTR(r1); \
|
|
std r8,_XER(r1); \
|
|
li r3,(n)+1; /* indicate partial regs in trap */ \
|
|
std r9,0(r1); /* store stack frame back link */ \
|
|
std r10,_CCR(r1); /* store orig CR in stackframe */ \
|
|
std r9,GPR1(r1); /* store stack frame back link */ \
|
|
std r11,SOFTE(r1); /* and save it to stackframe */ \
|
|
std r12,STACK_FRAME_OVERHEAD-16(r1); /* mark the frame */ \
|
|
std r3,_TRAP(r1); /* set trap number */ \
|
|
std r0,RESULT(r1); /* clear regs->result */
|
|
|
|
#define EXCEPTION_COMMON(n) \
|
|
EXCEPTION_COMMON_LVL(n, SPRN_SPRG_GEN_SCRATCH, PACA_EXGEN)
|
|
#define EXCEPTION_COMMON_CRIT(n) \
|
|
EXCEPTION_COMMON_LVL(n, SPRN_SPRG_CRIT_SCRATCH, PACA_EXCRIT)
|
|
#define EXCEPTION_COMMON_MC(n) \
|
|
EXCEPTION_COMMON_LVL(n, SPRN_SPRG_MC_SCRATCH, PACA_EXMC)
|
|
#define EXCEPTION_COMMON_DBG(n) \
|
|
EXCEPTION_COMMON_LVL(n, SPRN_SPRG_DBG_SCRATCH, PACA_EXDBG)
|
|
|
|
/*
|
|
* This is meant for exceptions that don't immediately hard-enable. We
|
|
* set a bit in paca->irq_happened to ensure that a subsequent call to
|
|
* arch_local_irq_restore() will properly hard-enable and avoid the
|
|
* fast-path, and then reconcile irq state.
|
|
*/
|
|
#define INTS_DISABLE RECONCILE_IRQ_STATE(r3,r4)
|
|
|
|
/*
|
|
* This is called by exceptions that don't use INTS_DISABLE (that did not
|
|
* touch irq indicators in the PACA). This will restore MSR:EE to it's
|
|
* previous value
|
|
*
|
|
* XXX In the long run, we may want to open-code it in order to separate the
|
|
* load from the wrtee, thus limiting the latency caused by the dependency
|
|
* but at this point, I'll favor code clarity until we have a near to final
|
|
* implementation
|
|
*/
|
|
#define INTS_RESTORE_HARD \
|
|
ld r11,_MSR(r1); \
|
|
wrtee r11;
|
|
|
|
/* XXX FIXME: Restore r14/r15 when necessary */
|
|
#define BAD_STACK_TRAMPOLINE(n) \
|
|
exc_##n##_bad_stack: \
|
|
li r1,(n); /* get exception number */ \
|
|
sth r1,PACA_TRAP_SAVE(r13); /* store trap */ \
|
|
b bad_stack_book3e; /* bad stack error */
|
|
|
|
/* WARNING: If you change the layout of this stub, make sure you check
|
|
* the debug exception handler which handles single stepping
|
|
* into exceptions from userspace, and the MM code in
|
|
* arch/powerpc/mm/tlb_nohash.c which patches the branch here
|
|
* and would need to be updated if that branch is moved
|
|
*/
|
|
#define EXCEPTION_STUB(loc, label) \
|
|
. = interrupt_base_book3e + loc; \
|
|
nop; /* To make debug interrupts happy */ \
|
|
b exc_##label##_book3e;
|
|
|
|
#define ACK_NONE(r)
|
|
#define ACK_DEC(r) \
|
|
lis r,TSR_DIS@h; \
|
|
mtspr SPRN_TSR,r
|
|
#define ACK_FIT(r) \
|
|
lis r,TSR_FIS@h; \
|
|
mtspr SPRN_TSR,r
|
|
|
|
/* Used by asynchronous interrupt that may happen in the idle loop.
|
|
*
|
|
* This check if the thread was in the idle loop, and if yes, returns
|
|
* to the caller rather than the PC. This is to avoid a race if
|
|
* interrupts happen before the wait instruction.
|
|
*/
|
|
#define CHECK_NAPPING() \
|
|
ld r11, PACA_THREAD_INFO(r13); \
|
|
ld r10,TI_LOCAL_FLAGS(r11); \
|
|
andi. r9,r10,_TLF_NAPPING; \
|
|
beq+ 1f; \
|
|
ld r8,_LINK(r1); \
|
|
rlwinm r7,r10,0,~_TLF_NAPPING; \
|
|
std r8,_NIP(r1); \
|
|
std r7,TI_LOCAL_FLAGS(r11); \
|
|
1:
|
|
|
|
|
|
#define MASKABLE_EXCEPTION(trapnum, intnum, label, hdlr, ack) \
|
|
START_EXCEPTION(label); \
|
|
NORMAL_EXCEPTION_PROLOG(trapnum, intnum, PROLOG_ADDITION_MASKABLE)\
|
|
EXCEPTION_COMMON(trapnum) \
|
|
INTS_DISABLE; \
|
|
ack(r8); \
|
|
CHECK_NAPPING(); \
|
|
addi r3,r1,STACK_FRAME_OVERHEAD; \
|
|
bl hdlr; \
|
|
b ret_from_except_lite;
|
|
|
|
/* This value is used to mark exception frames on the stack. */
|
|
.section ".toc","aw"
|
|
exception_marker:
|
|
.tc ID_EXC_MARKER[TC],STACK_FRAME_REGS_MARKER
|
|
|
|
|
|
/*
|
|
* And here we have the exception vectors !
|
|
*/
|
|
|
|
.text
|
|
.balign 0x1000
|
|
.globl interrupt_base_book3e
|
|
interrupt_base_book3e: /* fake trap */
|
|
EXCEPTION_STUB(0x000, machine_check)
|
|
EXCEPTION_STUB(0x020, critical_input) /* 0x0100 */
|
|
EXCEPTION_STUB(0x040, debug_crit) /* 0x0d00 */
|
|
EXCEPTION_STUB(0x060, data_storage) /* 0x0300 */
|
|
EXCEPTION_STUB(0x080, instruction_storage) /* 0x0400 */
|
|
EXCEPTION_STUB(0x0a0, external_input) /* 0x0500 */
|
|
EXCEPTION_STUB(0x0c0, alignment) /* 0x0600 */
|
|
EXCEPTION_STUB(0x0e0, program) /* 0x0700 */
|
|
EXCEPTION_STUB(0x100, fp_unavailable) /* 0x0800 */
|
|
EXCEPTION_STUB(0x120, system_call) /* 0x0c00 */
|
|
EXCEPTION_STUB(0x140, ap_unavailable) /* 0x0f20 */
|
|
EXCEPTION_STUB(0x160, decrementer) /* 0x0900 */
|
|
EXCEPTION_STUB(0x180, fixed_interval) /* 0x0980 */
|
|
EXCEPTION_STUB(0x1a0, watchdog) /* 0x09f0 */
|
|
EXCEPTION_STUB(0x1c0, data_tlb_miss)
|
|
EXCEPTION_STUB(0x1e0, instruction_tlb_miss)
|
|
EXCEPTION_STUB(0x200, altivec_unavailable)
|
|
EXCEPTION_STUB(0x220, altivec_assist)
|
|
EXCEPTION_STUB(0x260, perfmon)
|
|
EXCEPTION_STUB(0x280, doorbell)
|
|
EXCEPTION_STUB(0x2a0, doorbell_crit)
|
|
EXCEPTION_STUB(0x2c0, guest_doorbell)
|
|
EXCEPTION_STUB(0x2e0, guest_doorbell_crit)
|
|
EXCEPTION_STUB(0x300, hypercall)
|
|
EXCEPTION_STUB(0x320, ehpriv)
|
|
EXCEPTION_STUB(0x340, lrat_error)
|
|
|
|
.globl __end_interrupts
|
|
__end_interrupts:
|
|
|
|
/* Critical Input Interrupt */
|
|
START_EXCEPTION(critical_input);
|
|
CRIT_EXCEPTION_PROLOG(0x100, BOOKE_INTERRUPT_CRITICAL,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON_CRIT(0x100)
|
|
bl save_nvgprs
|
|
bl special_reg_save
|
|
CHECK_NAPPING();
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl unknown_exception
|
|
b ret_from_crit_except
|
|
|
|
/* Machine Check Interrupt */
|
|
START_EXCEPTION(machine_check);
|
|
MC_EXCEPTION_PROLOG(0x000, BOOKE_INTERRUPT_MACHINE_CHECK,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON_MC(0x000)
|
|
bl save_nvgprs
|
|
bl special_reg_save
|
|
CHECK_NAPPING();
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl machine_check_exception
|
|
b ret_from_mc_except
|
|
|
|
/* Data Storage Interrupt */
|
|
START_EXCEPTION(data_storage)
|
|
NORMAL_EXCEPTION_PROLOG(0x300, BOOKE_INTERRUPT_DATA_STORAGE,
|
|
PROLOG_ADDITION_2REGS)
|
|
mfspr r14,SPRN_DEAR
|
|
mfspr r15,SPRN_ESR
|
|
EXCEPTION_COMMON(0x300)
|
|
INTS_DISABLE
|
|
b storage_fault_common
|
|
|
|
/* Instruction Storage Interrupt */
|
|
START_EXCEPTION(instruction_storage);
|
|
NORMAL_EXCEPTION_PROLOG(0x400, BOOKE_INTERRUPT_INST_STORAGE,
|
|
PROLOG_ADDITION_2REGS)
|
|
li r15,0
|
|
mr r14,r10
|
|
EXCEPTION_COMMON(0x400)
|
|
INTS_DISABLE
|
|
b storage_fault_common
|
|
|
|
/* External Input Interrupt */
|
|
MASKABLE_EXCEPTION(0x500, BOOKE_INTERRUPT_EXTERNAL,
|
|
external_input, do_IRQ, ACK_NONE)
|
|
|
|
/* Alignment */
|
|
START_EXCEPTION(alignment);
|
|
NORMAL_EXCEPTION_PROLOG(0x600, BOOKE_INTERRUPT_ALIGNMENT,
|
|
PROLOG_ADDITION_2REGS)
|
|
mfspr r14,SPRN_DEAR
|
|
mfspr r15,SPRN_ESR
|
|
EXCEPTION_COMMON(0x600)
|
|
b alignment_more /* no room, go out of line */
|
|
|
|
/* Program Interrupt */
|
|
START_EXCEPTION(program);
|
|
NORMAL_EXCEPTION_PROLOG(0x700, BOOKE_INTERRUPT_PROGRAM,
|
|
PROLOG_ADDITION_1REG)
|
|
mfspr r14,SPRN_ESR
|
|
EXCEPTION_COMMON(0x700)
|
|
INTS_DISABLE
|
|
std r14,_DSISR(r1)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
ld r14,PACA_EXGEN+EX_R14(r13)
|
|
bl save_nvgprs
|
|
bl program_check_exception
|
|
b ret_from_except
|
|
|
|
/* Floating Point Unavailable Interrupt */
|
|
START_EXCEPTION(fp_unavailable);
|
|
NORMAL_EXCEPTION_PROLOG(0x800, BOOKE_INTERRUPT_FP_UNAVAIL,
|
|
PROLOG_ADDITION_NONE)
|
|
/* we can probably do a shorter exception entry for that one... */
|
|
EXCEPTION_COMMON(0x800)
|
|
ld r12,_MSR(r1)
|
|
andi. r0,r12,MSR_PR;
|
|
beq- 1f
|
|
bl load_up_fpu
|
|
b fast_exception_return
|
|
1: INTS_DISABLE
|
|
bl save_nvgprs
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl kernel_fp_unavailable_exception
|
|
b ret_from_except
|
|
|
|
/* Altivec Unavailable Interrupt */
|
|
START_EXCEPTION(altivec_unavailable);
|
|
NORMAL_EXCEPTION_PROLOG(0x200, BOOKE_INTERRUPT_ALTIVEC_UNAVAIL,
|
|
PROLOG_ADDITION_NONE)
|
|
/* we can probably do a shorter exception entry for that one... */
|
|
EXCEPTION_COMMON(0x200)
|
|
#ifdef CONFIG_ALTIVEC
|
|
BEGIN_FTR_SECTION
|
|
ld r12,_MSR(r1)
|
|
andi. r0,r12,MSR_PR;
|
|
beq- 1f
|
|
bl load_up_altivec
|
|
b fast_exception_return
|
|
1:
|
|
END_FTR_SECTION_IFSET(CPU_FTR_ALTIVEC)
|
|
#endif
|
|
INTS_DISABLE
|
|
bl save_nvgprs
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl altivec_unavailable_exception
|
|
b ret_from_except
|
|
|
|
/* AltiVec Assist */
|
|
START_EXCEPTION(altivec_assist);
|
|
NORMAL_EXCEPTION_PROLOG(0x220,
|
|
BOOKE_INTERRUPT_ALTIVEC_ASSIST,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON(0x220)
|
|
INTS_DISABLE
|
|
bl save_nvgprs
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
#ifdef CONFIG_ALTIVEC
|
|
BEGIN_FTR_SECTION
|
|
bl altivec_assist_exception
|
|
END_FTR_SECTION_IFSET(CPU_FTR_ALTIVEC)
|
|
#else
|
|
bl unknown_exception
|
|
#endif
|
|
b ret_from_except
|
|
|
|
|
|
/* Decrementer Interrupt */
|
|
MASKABLE_EXCEPTION(0x900, BOOKE_INTERRUPT_DECREMENTER,
|
|
decrementer, timer_interrupt, ACK_DEC)
|
|
|
|
/* Fixed Interval Timer Interrupt */
|
|
MASKABLE_EXCEPTION(0x980, BOOKE_INTERRUPT_FIT,
|
|
fixed_interval, unknown_exception, ACK_FIT)
|
|
|
|
/* Watchdog Timer Interrupt */
|
|
START_EXCEPTION(watchdog);
|
|
CRIT_EXCEPTION_PROLOG(0x9f0, BOOKE_INTERRUPT_WATCHDOG,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON_CRIT(0x9f0)
|
|
bl save_nvgprs
|
|
bl special_reg_save
|
|
CHECK_NAPPING();
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
#ifdef CONFIG_BOOKE_WDT
|
|
bl WatchdogException
|
|
#else
|
|
bl unknown_exception
|
|
#endif
|
|
b ret_from_crit_except
|
|
|
|
/* System Call Interrupt */
|
|
START_EXCEPTION(system_call)
|
|
mr r9,r13 /* keep a copy of userland r13 */
|
|
mfspr r11,SPRN_SRR0 /* get return address */
|
|
mfspr r12,SPRN_SRR1 /* get previous MSR */
|
|
mfspr r13,SPRN_SPRG_PACA /* get our PACA */
|
|
b system_call_common
|
|
|
|
/* Auxiliary Processor Unavailable Interrupt */
|
|
START_EXCEPTION(ap_unavailable);
|
|
NORMAL_EXCEPTION_PROLOG(0xf20, BOOKE_INTERRUPT_AP_UNAVAIL,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON(0xf20)
|
|
INTS_DISABLE
|
|
bl save_nvgprs
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl unknown_exception
|
|
b ret_from_except
|
|
|
|
/* Debug exception as a critical interrupt*/
|
|
START_EXCEPTION(debug_crit);
|
|
CRIT_EXCEPTION_PROLOG(0xd00, BOOKE_INTERRUPT_DEBUG,
|
|
PROLOG_ADDITION_2REGS)
|
|
|
|
/*
|
|
* If there is a single step or branch-taken exception in an
|
|
* exception entry sequence, it was probably meant to apply to
|
|
* the code where the exception occurred (since exception entry
|
|
* doesn't turn off DE automatically). We simulate the effect
|
|
* of turning off DE on entry to an exception handler by turning
|
|
* off DE in the CSRR1 value and clearing the debug status.
|
|
*/
|
|
|
|
mfspr r14,SPRN_DBSR /* check single-step/branch taken */
|
|
andis. r15,r14,(DBSR_IC|DBSR_BT)@h
|
|
beq+ 1f
|
|
|
|
#ifdef CONFIG_RELOCATABLE
|
|
ld r15,PACATOC(r13)
|
|
ld r14,interrupt_base_book3e@got(r15)
|
|
ld r15,__end_interrupts@got(r15)
|
|
cmpld cr0,r10,r14
|
|
cmpld cr1,r10,r15
|
|
#else
|
|
LOAD_REG_IMMEDIATE_SYM(r14, r15, interrupt_base_book3e)
|
|
cmpld cr0, r10, r14
|
|
LOAD_REG_IMMEDIATE_SYM(r14, r15, __end_interrupts)
|
|
cmpld cr1, r10, r14
|
|
#endif
|
|
blt+ cr0,1f
|
|
bge+ cr1,1f
|
|
|
|
/* here it looks like we got an inappropriate debug exception. */
|
|
lis r14,(DBSR_IC|DBSR_BT)@h /* clear the event */
|
|
rlwinm r11,r11,0,~MSR_DE /* clear DE in the CSRR1 value */
|
|
mtspr SPRN_DBSR,r14
|
|
mtspr SPRN_CSRR1,r11
|
|
lwz r10,PACA_EXCRIT+EX_CR(r13) /* restore registers */
|
|
ld r1,PACA_EXCRIT+EX_R1(r13)
|
|
ld r14,PACA_EXCRIT+EX_R14(r13)
|
|
ld r15,PACA_EXCRIT+EX_R15(r13)
|
|
mtcr r10
|
|
ld r10,PACA_EXCRIT+EX_R10(r13) /* restore registers */
|
|
ld r11,PACA_EXCRIT+EX_R11(r13)
|
|
mfspr r13,SPRN_SPRG_CRIT_SCRATCH
|
|
rfci
|
|
|
|
/* Normal debug exception */
|
|
/* XXX We only handle coming from userspace for now since we can't
|
|
* quite save properly an interrupted kernel state yet
|
|
*/
|
|
1: andi. r14,r11,MSR_PR; /* check for userspace again */
|
|
beq kernel_dbg_exc; /* if from kernel mode */
|
|
|
|
/* Now we mash up things to make it look like we are coming on a
|
|
* normal exception
|
|
*/
|
|
mfspr r14,SPRN_DBSR
|
|
EXCEPTION_COMMON_CRIT(0xd00)
|
|
std r14,_DSISR(r1)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
mr r4,r14
|
|
ld r14,PACA_EXCRIT+EX_R14(r13)
|
|
ld r15,PACA_EXCRIT+EX_R15(r13)
|
|
bl save_nvgprs
|
|
bl DebugException
|
|
b ret_from_except
|
|
|
|
kernel_dbg_exc:
|
|
b . /* NYI */
|
|
|
|
/* Debug exception as a debug interrupt*/
|
|
START_EXCEPTION(debug_debug);
|
|
DBG_EXCEPTION_PROLOG(0xd00, BOOKE_INTERRUPT_DEBUG,
|
|
PROLOG_ADDITION_2REGS)
|
|
|
|
/*
|
|
* If there is a single step or branch-taken exception in an
|
|
* exception entry sequence, it was probably meant to apply to
|
|
* the code where the exception occurred (since exception entry
|
|
* doesn't turn off DE automatically). We simulate the effect
|
|
* of turning off DE on entry to an exception handler by turning
|
|
* off DE in the DSRR1 value and clearing the debug status.
|
|
*/
|
|
|
|
mfspr r14,SPRN_DBSR /* check single-step/branch taken */
|
|
andis. r15,r14,(DBSR_IC|DBSR_BT)@h
|
|
beq+ 1f
|
|
|
|
#ifdef CONFIG_RELOCATABLE
|
|
ld r15,PACATOC(r13)
|
|
ld r14,interrupt_base_book3e@got(r15)
|
|
ld r15,__end_interrupts@got(r15)
|
|
cmpld cr0,r10,r14
|
|
cmpld cr1,r10,r15
|
|
#else
|
|
LOAD_REG_IMMEDIATE_SYM(r14, r15, interrupt_base_book3e)
|
|
cmpld cr0, r10, r14
|
|
LOAD_REG_IMMEDIATE_SYM(r14, r15,__end_interrupts)
|
|
cmpld cr1, r10, r14
|
|
#endif
|
|
blt+ cr0,1f
|
|
bge+ cr1,1f
|
|
|
|
/* here it looks like we got an inappropriate debug exception. */
|
|
lis r14,(DBSR_IC|DBSR_BT)@h /* clear the event */
|
|
rlwinm r11,r11,0,~MSR_DE /* clear DE in the DSRR1 value */
|
|
mtspr SPRN_DBSR,r14
|
|
mtspr SPRN_DSRR1,r11
|
|
lwz r10,PACA_EXDBG+EX_CR(r13) /* restore registers */
|
|
ld r1,PACA_EXDBG+EX_R1(r13)
|
|
ld r14,PACA_EXDBG+EX_R14(r13)
|
|
ld r15,PACA_EXDBG+EX_R15(r13)
|
|
mtcr r10
|
|
ld r10,PACA_EXDBG+EX_R10(r13) /* restore registers */
|
|
ld r11,PACA_EXDBG+EX_R11(r13)
|
|
mfspr r13,SPRN_SPRG_DBG_SCRATCH
|
|
rfdi
|
|
|
|
/* Normal debug exception */
|
|
/* XXX We only handle coming from userspace for now since we can't
|
|
* quite save properly an interrupted kernel state yet
|
|
*/
|
|
1: andi. r14,r11,MSR_PR; /* check for userspace again */
|
|
beq kernel_dbg_exc; /* if from kernel mode */
|
|
|
|
/* Now we mash up things to make it look like we are coming on a
|
|
* normal exception
|
|
*/
|
|
mfspr r14,SPRN_DBSR
|
|
EXCEPTION_COMMON_DBG(0xd08)
|
|
INTS_DISABLE
|
|
std r14,_DSISR(r1)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
mr r4,r14
|
|
ld r14,PACA_EXDBG+EX_R14(r13)
|
|
ld r15,PACA_EXDBG+EX_R15(r13)
|
|
bl save_nvgprs
|
|
bl DebugException
|
|
b ret_from_except
|
|
|
|
START_EXCEPTION(perfmon);
|
|
NORMAL_EXCEPTION_PROLOG(0x260, BOOKE_INTERRUPT_PERFORMANCE_MONITOR,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON(0x260)
|
|
INTS_DISABLE
|
|
CHECK_NAPPING()
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl performance_monitor_exception
|
|
b ret_from_except_lite
|
|
|
|
/* Doorbell interrupt */
|
|
MASKABLE_EXCEPTION(0x280, BOOKE_INTERRUPT_DOORBELL,
|
|
doorbell, doorbell_exception, ACK_NONE)
|
|
|
|
/* Doorbell critical Interrupt */
|
|
START_EXCEPTION(doorbell_crit);
|
|
CRIT_EXCEPTION_PROLOG(0x2a0, BOOKE_INTERRUPT_DOORBELL_CRITICAL,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON_CRIT(0x2a0)
|
|
bl save_nvgprs
|
|
bl special_reg_save
|
|
CHECK_NAPPING();
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl unknown_exception
|
|
b ret_from_crit_except
|
|
|
|
/*
|
|
* Guest doorbell interrupt
|
|
* This general exception use GSRRx save/restore registers
|
|
*/
|
|
START_EXCEPTION(guest_doorbell);
|
|
GDBELL_EXCEPTION_PROLOG(0x2c0, BOOKE_INTERRUPT_GUEST_DBELL,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON(0x2c0)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl save_nvgprs
|
|
INTS_RESTORE_HARD
|
|
bl unknown_exception
|
|
b ret_from_except
|
|
|
|
/* Guest Doorbell critical Interrupt */
|
|
START_EXCEPTION(guest_doorbell_crit);
|
|
CRIT_EXCEPTION_PROLOG(0x2e0, BOOKE_INTERRUPT_GUEST_DBELL_CRIT,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON_CRIT(0x2e0)
|
|
bl save_nvgprs
|
|
bl special_reg_save
|
|
CHECK_NAPPING();
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl unknown_exception
|
|
b ret_from_crit_except
|
|
|
|
/* Hypervisor call */
|
|
START_EXCEPTION(hypercall);
|
|
NORMAL_EXCEPTION_PROLOG(0x310, BOOKE_INTERRUPT_HV_SYSCALL,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON(0x310)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl save_nvgprs
|
|
INTS_RESTORE_HARD
|
|
bl unknown_exception
|
|
b ret_from_except
|
|
|
|
/* Embedded Hypervisor priviledged */
|
|
START_EXCEPTION(ehpriv);
|
|
NORMAL_EXCEPTION_PROLOG(0x320, BOOKE_INTERRUPT_HV_PRIV,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON(0x320)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl save_nvgprs
|
|
INTS_RESTORE_HARD
|
|
bl unknown_exception
|
|
b ret_from_except
|
|
|
|
/* LRAT Error interrupt */
|
|
START_EXCEPTION(lrat_error);
|
|
NORMAL_EXCEPTION_PROLOG(0x340, BOOKE_INTERRUPT_LRAT_ERROR,
|
|
PROLOG_ADDITION_NONE)
|
|
EXCEPTION_COMMON(0x340)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl save_nvgprs
|
|
INTS_RESTORE_HARD
|
|
bl unknown_exception
|
|
b ret_from_except
|
|
|
|
/*
|
|
* An interrupt came in while soft-disabled; We mark paca->irq_happened
|
|
* accordingly and if the interrupt is level sensitive, we hard disable
|
|
* hard disable (full_mask) corresponds to PACA_IRQ_MUST_HARD_MASK, so
|
|
* keep these in synch.
|
|
*/
|
|
|
|
.macro masked_interrupt_book3e paca_irq full_mask
|
|
lbz r10,PACAIRQHAPPENED(r13)
|
|
.if \full_mask == 1
|
|
ori r10,r10,\paca_irq | PACA_IRQ_HARD_DIS
|
|
.else
|
|
ori r10,r10,\paca_irq
|
|
.endif
|
|
stb r10,PACAIRQHAPPENED(r13)
|
|
|
|
.if \full_mask == 1
|
|
rldicl r10,r11,48,1 /* clear MSR_EE */
|
|
rotldi r11,r10,16
|
|
mtspr SPRN_SRR1,r11
|
|
.endif
|
|
|
|
lwz r11,PACA_EXGEN+EX_CR(r13)
|
|
mtcr r11
|
|
ld r10,PACA_EXGEN+EX_R10(r13)
|
|
ld r11,PACA_EXGEN+EX_R11(r13)
|
|
mfspr r13,SPRN_SPRG_GEN_SCRATCH
|
|
rfi
|
|
b .
|
|
.endm
|
|
|
|
masked_interrupt_book3e_0x500:
|
|
// XXX When adding support for EPR, use PACA_IRQ_EE_EDGE
|
|
masked_interrupt_book3e PACA_IRQ_EE 1
|
|
|
|
masked_interrupt_book3e_0x900:
|
|
ACK_DEC(r10);
|
|
masked_interrupt_book3e PACA_IRQ_DEC 0
|
|
|
|
masked_interrupt_book3e_0x980:
|
|
ACK_FIT(r10);
|
|
masked_interrupt_book3e PACA_IRQ_DEC 0
|
|
|
|
masked_interrupt_book3e_0x280:
|
|
masked_interrupt_book3e_0x2c0:
|
|
masked_interrupt_book3e PACA_IRQ_DBELL 0
|
|
|
|
/*
|
|
* This is called from 0x300 and 0x400 handlers after the prologs with
|
|
* r14 and r15 containing the fault address and error code, with the
|
|
* original values stashed away in the PACA
|
|
*/
|
|
storage_fault_common:
|
|
std r14,_DAR(r1)
|
|
std r15,_DSISR(r1)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
mr r4,r14
|
|
mr r5,r15
|
|
ld r14,PACA_EXGEN+EX_R14(r13)
|
|
ld r15,PACA_EXGEN+EX_R15(r13)
|
|
bl do_page_fault
|
|
cmpdi r3,0
|
|
bne- 1f
|
|
b ret_from_except_lite
|
|
1: bl save_nvgprs
|
|
mr r5,r3
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
ld r4,_DAR(r1)
|
|
bl bad_page_fault
|
|
b ret_from_except
|
|
|
|
/*
|
|
* Alignment exception doesn't fit entirely in the 0x100 bytes so it
|
|
* continues here.
|
|
*/
|
|
alignment_more:
|
|
std r14,_DAR(r1)
|
|
std r15,_DSISR(r1)
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
ld r14,PACA_EXGEN+EX_R14(r13)
|
|
ld r15,PACA_EXGEN+EX_R15(r13)
|
|
bl save_nvgprs
|
|
INTS_RESTORE_HARD
|
|
bl alignment_exception
|
|
b ret_from_except
|
|
|
|
.align 7
|
|
_GLOBAL(ret_from_except)
|
|
ld r11,_TRAP(r1)
|
|
andi. r0,r11,1
|
|
bne ret_from_except_lite
|
|
REST_NVGPRS(r1)
|
|
|
|
_GLOBAL(ret_from_except_lite)
|
|
/*
|
|
* Disable interrupts so that current_thread_info()->flags
|
|
* can't change between when we test it and when we return
|
|
* from the interrupt.
|
|
*/
|
|
wrteei 0
|
|
|
|
ld r9, PACA_THREAD_INFO(r13)
|
|
ld r3,_MSR(r1)
|
|
ld r10,PACACURRENT(r13)
|
|
ld r4,TI_FLAGS(r9)
|
|
andi. r3,r3,MSR_PR
|
|
beq resume_kernel
|
|
lwz r3,(THREAD+THREAD_DBCR0)(r10)
|
|
|
|
/* Check current_thread_info()->flags */
|
|
andi. r0,r4,_TIF_USER_WORK_MASK
|
|
bne 1f
|
|
/*
|
|
* Check to see if the dbcr0 register is set up to debug.
|
|
* Use the internal debug mode bit to do this.
|
|
*/
|
|
andis. r0,r3,DBCR0_IDM@h
|
|
beq restore
|
|
mfmsr r0
|
|
rlwinm r0,r0,0,~MSR_DE /* Clear MSR.DE */
|
|
mtmsr r0
|
|
mtspr SPRN_DBCR0,r3
|
|
li r10, -1
|
|
mtspr SPRN_DBSR,r10
|
|
b restore
|
|
1: andi. r0,r4,_TIF_NEED_RESCHED
|
|
beq 2f
|
|
bl restore_interrupts
|
|
SCHEDULE_USER
|
|
b ret_from_except_lite
|
|
2:
|
|
bl save_nvgprs
|
|
/*
|
|
* Use a non volatile GPR to save and restore our thread_info flags
|
|
* across the call to restore_interrupts.
|
|
*/
|
|
mr r30,r4
|
|
bl restore_interrupts
|
|
mr r4,r30
|
|
addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl do_notify_resume
|
|
b ret_from_except
|
|
|
|
resume_kernel:
|
|
/* check current_thread_info, _TIF_EMULATE_STACK_STORE */
|
|
andis. r8,r4,_TIF_EMULATE_STACK_STORE@h
|
|
beq+ 1f
|
|
|
|
addi r8,r1,INT_FRAME_SIZE /* Get the kprobed function entry */
|
|
|
|
ld r3,GPR1(r1)
|
|
subi r3,r3,INT_FRAME_SIZE /* dst: Allocate a trampoline exception frame */
|
|
mr r4,r1 /* src: current exception frame */
|
|
mr r1,r3 /* Reroute the trampoline frame to r1 */
|
|
|
|
/* Copy from the original to the trampoline. */
|
|
li r5,INT_FRAME_SIZE/8 /* size: INT_FRAME_SIZE */
|
|
li r6,0 /* start offset: 0 */
|
|
mtctr r5
|
|
2: ldx r0,r6,r4
|
|
stdx r0,r6,r3
|
|
addi r6,r6,8
|
|
bdnz 2b
|
|
|
|
/* Do real store operation to complete stdu */
|
|
ld r5,GPR1(r1)
|
|
std r8,0(r5)
|
|
|
|
/* Clear _TIF_EMULATE_STACK_STORE flag */
|
|
lis r11,_TIF_EMULATE_STACK_STORE@h
|
|
addi r5,r9,TI_FLAGS
|
|
0: ldarx r4,0,r5
|
|
andc r4,r4,r11
|
|
stdcx. r4,0,r5
|
|
bne- 0b
|
|
1:
|
|
|
|
#ifdef CONFIG_PREEMPT
|
|
/* Check if we need to preempt */
|
|
andi. r0,r4,_TIF_NEED_RESCHED
|
|
beq+ restore
|
|
/* Check that preempt_count() == 0 and interrupts are enabled */
|
|
lwz r8,TI_PREEMPT(r9)
|
|
cmpwi cr0,r8,0
|
|
bne restore
|
|
ld r0,SOFTE(r1)
|
|
andi. r0,r0,IRQS_DISABLED
|
|
bne restore
|
|
|
|
/*
|
|
* Here we are preempting the current task. We want to make
|
|
* sure we are soft-disabled first and reconcile irq state.
|
|
*/
|
|
RECONCILE_IRQ_STATE(r3,r4)
|
|
bl preempt_schedule_irq
|
|
|
|
/*
|
|
* arch_local_irq_restore() from preempt_schedule_irq above may
|
|
* enable hard interrupt but we really should disable interrupts
|
|
* when we return from the interrupt, and so that we don't get
|
|
* interrupted after loading SRR0/1.
|
|
*/
|
|
wrteei 0
|
|
#endif /* CONFIG_PREEMPT */
|
|
|
|
restore:
|
|
/*
|
|
* This is the main kernel exit path. First we check if we
|
|
* are about to re-enable interrupts
|
|
*/
|
|
ld r5,SOFTE(r1)
|
|
lbz r6,PACAIRQSOFTMASK(r13)
|
|
andi. r5,r5,IRQS_DISABLED
|
|
bne .Lrestore_irq_off
|
|
|
|
/* We are enabling, were we already enabled ? Yes, just return */
|
|
andi. r6,r6,IRQS_DISABLED
|
|
beq cr0,fast_exception_return
|
|
|
|
/*
|
|
* We are about to soft-enable interrupts (we are hard disabled
|
|
* at this point). We check if there's anything that needs to
|
|
* be replayed first.
|
|
*/
|
|
lbz r0,PACAIRQHAPPENED(r13)
|
|
cmpwi cr0,r0,0
|
|
bne- .Lrestore_check_irq_replay
|
|
|
|
/*
|
|
* Get here when nothing happened while soft-disabled, just
|
|
* soft-enable and move-on. We will hard-enable as a side
|
|
* effect of rfi
|
|
*/
|
|
.Lrestore_no_replay:
|
|
TRACE_ENABLE_INTS
|
|
li r0,IRQS_ENABLED
|
|
stb r0,PACAIRQSOFTMASK(r13);
|
|
|
|
/* This is the return from load_up_fpu fast path which could do with
|
|
* less GPR restores in fact, but for now we have a single return path
|
|
*/
|
|
fast_exception_return:
|
|
wrteei 0
|
|
1: mr r0,r13
|
|
ld r10,_MSR(r1)
|
|
REST_4GPRS(2, r1)
|
|
andi. r6,r10,MSR_PR
|
|
REST_2GPRS(6, r1)
|
|
beq 1f
|
|
ACCOUNT_CPU_USER_EXIT(r13, r10, r11)
|
|
ld r0,GPR13(r1)
|
|
|
|
1: stdcx. r0,0,r1 /* to clear the reservation */
|
|
|
|
ld r8,_CCR(r1)
|
|
ld r9,_LINK(r1)
|
|
ld r10,_CTR(r1)
|
|
ld r11,_XER(r1)
|
|
mtcr r8
|
|
mtlr r9
|
|
mtctr r10
|
|
mtxer r11
|
|
REST_2GPRS(8, r1)
|
|
ld r10,GPR10(r1)
|
|
ld r11,GPR11(r1)
|
|
ld r12,GPR12(r1)
|
|
mtspr SPRN_SPRG_GEN_SCRATCH,r0
|
|
|
|
std r10,PACA_EXGEN+EX_R10(r13);
|
|
std r11,PACA_EXGEN+EX_R11(r13);
|
|
ld r10,_NIP(r1)
|
|
ld r11,_MSR(r1)
|
|
ld r0,GPR0(r1)
|
|
ld r1,GPR1(r1)
|
|
mtspr SPRN_SRR0,r10
|
|
mtspr SPRN_SRR1,r11
|
|
ld r10,PACA_EXGEN+EX_R10(r13)
|
|
ld r11,PACA_EXGEN+EX_R11(r13)
|
|
mfspr r13,SPRN_SPRG_GEN_SCRATCH
|
|
rfi
|
|
|
|
/*
|
|
* We are returning to a context with interrupts soft disabled.
|
|
*
|
|
* However, we may also about to hard enable, so we need to
|
|
* make sure that in this case, we also clear PACA_IRQ_HARD_DIS
|
|
* or that bit can get out of sync and bad things will happen
|
|
*/
|
|
.Lrestore_irq_off:
|
|
ld r3,_MSR(r1)
|
|
lbz r7,PACAIRQHAPPENED(r13)
|
|
andi. r0,r3,MSR_EE
|
|
beq 1f
|
|
rlwinm r7,r7,0,~PACA_IRQ_HARD_DIS
|
|
stb r7,PACAIRQHAPPENED(r13)
|
|
1:
|
|
#if defined(CONFIG_PPC_IRQ_SOFT_MASK_DEBUG) && defined(CONFIG_BUG)
|
|
/* The interrupt should not have soft enabled. */
|
|
lbz r7,PACAIRQSOFTMASK(r13)
|
|
1: tdeqi r7,IRQS_ENABLED
|
|
EMIT_BUG_ENTRY 1b,__FILE__,__LINE__,BUGFLAG_WARNING
|
|
#endif
|
|
b fast_exception_return
|
|
|
|
/*
|
|
* Something did happen, check if a re-emit is needed
|
|
* (this also clears paca->irq_happened)
|
|
*/
|
|
.Lrestore_check_irq_replay:
|
|
/* XXX: We could implement a fast path here where we check
|
|
* for irq_happened being just 0x01, in which case we can
|
|
* clear it and return. That means that we would potentially
|
|
* miss a decrementer having wrapped all the way around.
|
|
*
|
|
* Still, this might be useful for things like hash_page
|
|
*/
|
|
bl __check_irq_replay
|
|
cmpwi cr0,r3,0
|
|
beq .Lrestore_no_replay
|
|
|
|
/*
|
|
* We need to re-emit an interrupt. We do so by re-using our
|
|
* existing exception frame. We first change the trap value,
|
|
* but we need to ensure we preserve the low nibble of it
|
|
*/
|
|
ld r4,_TRAP(r1)
|
|
clrldi r4,r4,60
|
|
or r4,r4,r3
|
|
std r4,_TRAP(r1)
|
|
|
|
/*
|
|
* PACA_IRQ_HARD_DIS won't always be set here, so set it now
|
|
* to reconcile the IRQ state. Tracing is already accounted for.
|
|
*/
|
|
lbz r4,PACAIRQHAPPENED(r13)
|
|
ori r4,r4,PACA_IRQ_HARD_DIS
|
|
stb r4,PACAIRQHAPPENED(r13)
|
|
|
|
/*
|
|
* Then find the right handler and call it. Interrupts are
|
|
* still soft-disabled and we keep them that way.
|
|
*/
|
|
cmpwi cr0,r3,0x500
|
|
bne 1f
|
|
addi r3,r1,STACK_FRAME_OVERHEAD;
|
|
bl do_IRQ
|
|
b ret_from_except
|
|
1: cmpwi cr0,r3,0xf00
|
|
bne 1f
|
|
addi r3,r1,STACK_FRAME_OVERHEAD;
|
|
bl performance_monitor_exception
|
|
b ret_from_except
|
|
1: cmpwi cr0,r3,0xe60
|
|
bne 1f
|
|
addi r3,r1,STACK_FRAME_OVERHEAD;
|
|
bl handle_hmi_exception
|
|
b ret_from_except
|
|
1: cmpwi cr0,r3,0x900
|
|
bne 1f
|
|
addi r3,r1,STACK_FRAME_OVERHEAD;
|
|
bl timer_interrupt
|
|
b ret_from_except
|
|
#ifdef CONFIG_PPC_DOORBELL
|
|
1:
|
|
cmpwi cr0,r3,0x280
|
|
bne 1f
|
|
addi r3,r1,STACK_FRAME_OVERHEAD;
|
|
bl doorbell_exception
|
|
#endif /* CONFIG_PPC_DOORBELL */
|
|
1: b ret_from_except /* What else to do here ? */
|
|
|
|
_ASM_NOKPROBE_SYMBOL(ret_from_except);
|
|
_ASM_NOKPROBE_SYMBOL(ret_from_except_lite);
|
|
_ASM_NOKPROBE_SYMBOL(resume_kernel);
|
|
_ASM_NOKPROBE_SYMBOL(restore);
|
|
_ASM_NOKPROBE_SYMBOL(fast_exception_return);
|
|
|
|
/*
|
|
* Trampolines used when spotting a bad kernel stack pointer in
|
|
* the exception entry code.
|
|
*
|
|
* TODO: move some bits like SRR0 read to trampoline, pass PACA
|
|
* index around, etc... to handle crit & mcheck
|
|
*/
|
|
BAD_STACK_TRAMPOLINE(0x000)
|
|
BAD_STACK_TRAMPOLINE(0x100)
|
|
BAD_STACK_TRAMPOLINE(0x200)
|
|
BAD_STACK_TRAMPOLINE(0x220)
|
|
BAD_STACK_TRAMPOLINE(0x260)
|
|
BAD_STACK_TRAMPOLINE(0x280)
|
|
BAD_STACK_TRAMPOLINE(0x2a0)
|
|
BAD_STACK_TRAMPOLINE(0x2c0)
|
|
BAD_STACK_TRAMPOLINE(0x2e0)
|
|
BAD_STACK_TRAMPOLINE(0x300)
|
|
BAD_STACK_TRAMPOLINE(0x310)
|
|
BAD_STACK_TRAMPOLINE(0x320)
|
|
BAD_STACK_TRAMPOLINE(0x340)
|
|
BAD_STACK_TRAMPOLINE(0x400)
|
|
BAD_STACK_TRAMPOLINE(0x500)
|
|
BAD_STACK_TRAMPOLINE(0x600)
|
|
BAD_STACK_TRAMPOLINE(0x700)
|
|
BAD_STACK_TRAMPOLINE(0x800)
|
|
BAD_STACK_TRAMPOLINE(0x900)
|
|
BAD_STACK_TRAMPOLINE(0x980)
|
|
BAD_STACK_TRAMPOLINE(0x9f0)
|
|
BAD_STACK_TRAMPOLINE(0xa00)
|
|
BAD_STACK_TRAMPOLINE(0xb00)
|
|
BAD_STACK_TRAMPOLINE(0xc00)
|
|
BAD_STACK_TRAMPOLINE(0xd00)
|
|
BAD_STACK_TRAMPOLINE(0xd08)
|
|
BAD_STACK_TRAMPOLINE(0xe00)
|
|
BAD_STACK_TRAMPOLINE(0xf00)
|
|
BAD_STACK_TRAMPOLINE(0xf20)
|
|
|
|
.globl bad_stack_book3e
|
|
bad_stack_book3e:
|
|
/* XXX: Needs to make SPRN_SPRG_GEN depend on exception type */
|
|
mfspr r10,SPRN_SRR0; /* read SRR0 before touching stack */
|
|
ld r1,PACAEMERGSP(r13)
|
|
subi r1,r1,64+INT_FRAME_SIZE
|
|
std r10,_NIP(r1)
|
|
std r11,_MSR(r1)
|
|
ld r10,PACA_EXGEN+EX_R1(r13) /* FIXME for crit & mcheck */
|
|
lwz r11,PACA_EXGEN+EX_CR(r13) /* FIXME for crit & mcheck */
|
|
std r10,GPR1(r1)
|
|
std r11,_CCR(r1)
|
|
mfspr r10,SPRN_DEAR
|
|
mfspr r11,SPRN_ESR
|
|
std r10,_DAR(r1)
|
|
std r11,_DSISR(r1)
|
|
std r0,GPR0(r1); /* save r0 in stackframe */ \
|
|
std r2,GPR2(r1); /* save r2 in stackframe */ \
|
|
SAVE_4GPRS(3, r1); /* save r3 - r6 in stackframe */ \
|
|
SAVE_2GPRS(7, r1); /* save r7, r8 in stackframe */ \
|
|
std r9,GPR9(r1); /* save r9 in stackframe */ \
|
|
ld r3,PACA_EXGEN+EX_R10(r13);/* get back r10 */ \
|
|
ld r4,PACA_EXGEN+EX_R11(r13);/* get back r11 */ \
|
|
mfspr r5,SPRN_SPRG_GEN_SCRATCH;/* get back r13 XXX can be wrong */ \
|
|
std r3,GPR10(r1); /* save r10 to stackframe */ \
|
|
std r4,GPR11(r1); /* save r11 to stackframe */ \
|
|
std r12,GPR12(r1); /* save r12 in stackframe */ \
|
|
std r5,GPR13(r1); /* save it to stackframe */ \
|
|
mflr r10
|
|
mfctr r11
|
|
mfxer r12
|
|
std r10,_LINK(r1)
|
|
std r11,_CTR(r1)
|
|
std r12,_XER(r1)
|
|
SAVE_10GPRS(14,r1)
|
|
SAVE_8GPRS(24,r1)
|
|
lhz r12,PACA_TRAP_SAVE(r13)
|
|
std r12,_TRAP(r1)
|
|
addi r11,r1,INT_FRAME_SIZE
|
|
std r11,0(r1)
|
|
li r12,0
|
|
std r12,0(r11)
|
|
ld r2,PACATOC(r13)
|
|
1: addi r3,r1,STACK_FRAME_OVERHEAD
|
|
bl kernel_bad_stack
|
|
b 1b
|
|
|
|
/*
|
|
* Setup the initial TLB for a core. This current implementation
|
|
* assume that whatever we are running off will not conflict with
|
|
* the new mapping at PAGE_OFFSET.
|
|
*/
|
|
_GLOBAL(initial_tlb_book3e)
|
|
|
|
/* Look for the first TLB with IPROT set */
|
|
mfspr r4,SPRN_TLB0CFG
|
|
andi. r3,r4,TLBnCFG_IPROT
|
|
lis r3,MAS0_TLBSEL(0)@h
|
|
bne found_iprot
|
|
|
|
mfspr r4,SPRN_TLB1CFG
|
|
andi. r3,r4,TLBnCFG_IPROT
|
|
lis r3,MAS0_TLBSEL(1)@h
|
|
bne found_iprot
|
|
|
|
mfspr r4,SPRN_TLB2CFG
|
|
andi. r3,r4,TLBnCFG_IPROT
|
|
lis r3,MAS0_TLBSEL(2)@h
|
|
bne found_iprot
|
|
|
|
lis r3,MAS0_TLBSEL(3)@h
|
|
mfspr r4,SPRN_TLB3CFG
|
|
/* fall through */
|
|
|
|
found_iprot:
|
|
andi. r5,r4,TLBnCFG_HES
|
|
bne have_hes
|
|
|
|
mflr r8 /* save LR */
|
|
/* 1. Find the index of the entry we're executing in
|
|
*
|
|
* r3 = MAS0_TLBSEL (for the iprot array)
|
|
* r4 = SPRN_TLBnCFG
|
|
*/
|
|
bl invstr /* Find our address */
|
|
invstr: mflr r6 /* Make it accessible */
|
|
mfmsr r7
|
|
rlwinm r5,r7,27,31,31 /* extract MSR[IS] */
|
|
mfspr r7,SPRN_PID
|
|
slwi r7,r7,16
|
|
or r7,r7,r5
|
|
mtspr SPRN_MAS6,r7
|
|
tlbsx 0,r6 /* search MSR[IS], SPID=PID */
|
|
|
|
mfspr r3,SPRN_MAS0
|
|
rlwinm r5,r3,16,20,31 /* Extract MAS0(Entry) */
|
|
|
|
mfspr r7,SPRN_MAS1 /* Insure IPROT set */
|
|
oris r7,r7,MAS1_IPROT@h
|
|
mtspr SPRN_MAS1,r7
|
|
tlbwe
|
|
|
|
/* 2. Invalidate all entries except the entry we're executing in
|
|
*
|
|
* r3 = MAS0 w/TLBSEL & ESEL for the entry we are running in
|
|
* r4 = SPRN_TLBnCFG
|
|
* r5 = ESEL of entry we are running in
|
|
*/
|
|
andi. r4,r4,TLBnCFG_N_ENTRY /* Extract # entries */
|
|
li r6,0 /* Set Entry counter to 0 */
|
|
1: mr r7,r3 /* Set MAS0(TLBSEL) */
|
|
rlwimi r7,r6,16,4,15 /* Setup MAS0 = TLBSEL | ESEL(r6) */
|
|
mtspr SPRN_MAS0,r7
|
|
tlbre
|
|
mfspr r7,SPRN_MAS1
|
|
rlwinm r7,r7,0,2,31 /* Clear MAS1 Valid and IPROT */
|
|
cmpw r5,r6
|
|
beq skpinv /* Dont update the current execution TLB */
|
|
mtspr SPRN_MAS1,r7
|
|
tlbwe
|
|
isync
|
|
skpinv: addi r6,r6,1 /* Increment */
|
|
cmpw r6,r4 /* Are we done? */
|
|
bne 1b /* If not, repeat */
|
|
|
|
/* Invalidate all TLBs */
|
|
PPC_TLBILX_ALL(0,R0)
|
|
sync
|
|
isync
|
|
|
|
/* 3. Setup a temp mapping and jump to it
|
|
*
|
|
* r3 = MAS0 w/TLBSEL & ESEL for the entry we are running in
|
|
* r5 = ESEL of entry we are running in
|
|
*/
|
|
andi. r7,r5,0x1 /* Find an entry not used and is non-zero */
|
|
addi r7,r7,0x1
|
|
mr r4,r3 /* Set MAS0(TLBSEL) = 1 */
|
|
mtspr SPRN_MAS0,r4
|
|
tlbre
|
|
|
|
rlwimi r4,r7,16,4,15 /* Setup MAS0 = TLBSEL | ESEL(r7) */
|
|
mtspr SPRN_MAS0,r4
|
|
|
|
mfspr r7,SPRN_MAS1
|
|
xori r6,r7,MAS1_TS /* Setup TMP mapping in the other Address space */
|
|
mtspr SPRN_MAS1,r6
|
|
|
|
tlbwe
|
|
|
|
mfmsr r6
|
|
xori r6,r6,MSR_IS
|
|
mtspr SPRN_SRR1,r6
|
|
bl 1f /* Find our address */
|
|
1: mflr r6
|
|
addi r6,r6,(2f - 1b)
|
|
mtspr SPRN_SRR0,r6
|
|
rfi
|
|
2:
|
|
|
|
/* 4. Clear out PIDs & Search info
|
|
*
|
|
* r3 = MAS0 w/TLBSEL & ESEL for the entry we started in
|
|
* r4 = MAS0 w/TLBSEL & ESEL for the temp mapping
|
|
* r5 = MAS3
|
|
*/
|
|
li r6,0
|
|
mtspr SPRN_MAS6,r6
|
|
mtspr SPRN_PID,r6
|
|
|
|
/* 5. Invalidate mapping we started in
|
|
*
|
|
* r3 = MAS0 w/TLBSEL & ESEL for the entry we started in
|
|
* r4 = MAS0 w/TLBSEL & ESEL for the temp mapping
|
|
* r5 = MAS3
|
|
*/
|
|
mtspr SPRN_MAS0,r3
|
|
tlbre
|
|
mfspr r6,SPRN_MAS1
|
|
rlwinm r6,r6,0,2,31 /* clear IPROT and VALID */
|
|
mtspr SPRN_MAS1,r6
|
|
tlbwe
|
|
sync
|
|
isync
|
|
|
|
/* 6. Setup KERNELBASE mapping in TLB[0]
|
|
*
|
|
* r3 = MAS0 w/TLBSEL & ESEL for the entry we started in
|
|
* r4 = MAS0 w/TLBSEL & ESEL for the temp mapping
|
|
* r5 = MAS3
|
|
*/
|
|
rlwinm r3,r3,0,16,3 /* clear ESEL */
|
|
mtspr SPRN_MAS0,r3
|
|
lis r6,(MAS1_VALID|MAS1_IPROT)@h
|
|
ori r6,r6,(MAS1_TSIZE(BOOK3E_PAGESZ_1GB))@l
|
|
mtspr SPRN_MAS1,r6
|
|
|
|
LOAD_REG_IMMEDIATE(r6, PAGE_OFFSET | MAS2_M_IF_NEEDED)
|
|
mtspr SPRN_MAS2,r6
|
|
|
|
rlwinm r5,r5,0,0,25
|
|
ori r5,r5,MAS3_SR | MAS3_SW | MAS3_SX
|
|
mtspr SPRN_MAS3,r5
|
|
li r5,-1
|
|
rlwinm r5,r5,0,0,25
|
|
|
|
tlbwe
|
|
|
|
/* 7. Jump to KERNELBASE mapping
|
|
*
|
|
* r4 = MAS0 w/TLBSEL & ESEL for the temp mapping
|
|
*/
|
|
/* Now we branch the new virtual address mapped by this entry */
|
|
bl 1f /* Find our address */
|
|
1: mflr r6
|
|
addi r6,r6,(2f - 1b)
|
|
tovirt(r6,r6)
|
|
lis r7,MSR_KERNEL@h
|
|
ori r7,r7,MSR_KERNEL@l
|
|
mtspr SPRN_SRR0,r6
|
|
mtspr SPRN_SRR1,r7
|
|
rfi /* start execution out of TLB1[0] entry */
|
|
2:
|
|
|
|
/* 8. Clear out the temp mapping
|
|
*
|
|
* r4 = MAS0 w/TLBSEL & ESEL for the entry we are running in
|
|
*/
|
|
mtspr SPRN_MAS0,r4
|
|
tlbre
|
|
mfspr r5,SPRN_MAS1
|
|
rlwinm r5,r5,0,2,31 /* clear IPROT and VALID */
|
|
mtspr SPRN_MAS1,r5
|
|
tlbwe
|
|
sync
|
|
isync
|
|
|
|
/* We translate LR and return */
|
|
tovirt(r8,r8)
|
|
mtlr r8
|
|
blr
|
|
|
|
have_hes:
|
|
/* Setup MAS 0,1,2,3 and 7 for tlbwe of a 1G entry that maps the
|
|
* kernel linear mapping. We also set MAS8 once for all here though
|
|
* that will have to be made dependent on whether we are running under
|
|
* a hypervisor I suppose.
|
|
*/
|
|
|
|
/* BEWARE, MAGIC
|
|
* This code is called as an ordinary function on the boot CPU. But to
|
|
* avoid duplication, this code is also used in SCOM bringup of
|
|
* secondary CPUs. We read the code between the initial_tlb_code_start
|
|
* and initial_tlb_code_end labels one instruction at a time and RAM it
|
|
* into the new core via SCOM. That doesn't process branches, so there
|
|
* must be none between those two labels. It also means if this code
|
|
* ever takes any parameters, the SCOM code must also be updated to
|
|
* provide them.
|
|
*/
|
|
.globl a2_tlbinit_code_start
|
|
a2_tlbinit_code_start:
|
|
|
|
ori r11,r3,MAS0_WQ_ALLWAYS
|
|
oris r11,r11,MAS0_ESEL(3)@h /* Use way 3: workaround A2 erratum 376 */
|
|
mtspr SPRN_MAS0,r11
|
|
lis r3,(MAS1_VALID | MAS1_IPROT)@h
|
|
ori r3,r3,BOOK3E_PAGESZ_1GB << MAS1_TSIZE_SHIFT
|
|
mtspr SPRN_MAS1,r3
|
|
LOAD_REG_IMMEDIATE(r3, PAGE_OFFSET | MAS2_M)
|
|
mtspr SPRN_MAS2,r3
|
|
li r3,MAS3_SR | MAS3_SW | MAS3_SX
|
|
mtspr SPRN_MAS7_MAS3,r3
|
|
li r3,0
|
|
mtspr SPRN_MAS8,r3
|
|
|
|
/* Write the TLB entry */
|
|
tlbwe
|
|
|
|
.globl a2_tlbinit_after_linear_map
|
|
a2_tlbinit_after_linear_map:
|
|
|
|
/* Now we branch the new virtual address mapped by this entry */
|
|
LOAD_REG_IMMEDIATE_SYM(r3, r5, 1f)
|
|
mtctr r3
|
|
bctr
|
|
|
|
1: /* We are now running at PAGE_OFFSET, clean the TLB of everything
|
|
* else (including IPROTed things left by firmware)
|
|
* r4 = TLBnCFG
|
|
* r3 = current address (more or less)
|
|
*/
|
|
|
|
li r5,0
|
|
mtspr SPRN_MAS6,r5
|
|
tlbsx 0,r3
|
|
|
|
rlwinm r9,r4,0,TLBnCFG_N_ENTRY
|
|
rlwinm r10,r4,8,0xff
|
|
addi r10,r10,-1 /* Get inner loop mask */
|
|
|
|
li r3,1
|
|
|
|
mfspr r5,SPRN_MAS1
|
|
rlwinm r5,r5,0,(~(MAS1_VALID|MAS1_IPROT))
|
|
|
|
mfspr r6,SPRN_MAS2
|
|
rldicr r6,r6,0,51 /* Extract EPN */
|
|
|
|
mfspr r7,SPRN_MAS0
|
|
rlwinm r7,r7,0,0xffff0fff /* Clear HES and WQ */
|
|
|
|
rlwinm r8,r7,16,0xfff /* Extract ESEL */
|
|
|
|
2: add r4,r3,r8
|
|
and r4,r4,r10
|
|
|
|
rlwimi r7,r4,16,MAS0_ESEL_MASK
|
|
|
|
mtspr SPRN_MAS0,r7
|
|
mtspr SPRN_MAS1,r5
|
|
mtspr SPRN_MAS2,r6
|
|
tlbwe
|
|
|
|
addi r3,r3,1
|
|
and. r4,r3,r10
|
|
|
|
bne 3f
|
|
addis r6,r6,(1<<30)@h
|
|
3:
|
|
cmpw r3,r9
|
|
blt 2b
|
|
|
|
.globl a2_tlbinit_after_iprot_flush
|
|
a2_tlbinit_after_iprot_flush:
|
|
|
|
PPC_TLBILX(0,0,R0)
|
|
sync
|
|
isync
|
|
|
|
.globl a2_tlbinit_code_end
|
|
a2_tlbinit_code_end:
|
|
|
|
/* We translate LR and return */
|
|
mflr r3
|
|
tovirt(r3,r3)
|
|
mtlr r3
|
|
blr
|
|
|
|
/*
|
|
* Main entry (boot CPU, thread 0)
|
|
*
|
|
* We enter here from head_64.S, possibly after the prom_init trampoline
|
|
* with r3 and r4 already saved to r31 and 30 respectively and in 64 bits
|
|
* mode. Anything else is as it was left by the bootloader
|
|
*
|
|
* Initial requirements of this port:
|
|
*
|
|
* - Kernel loaded at 0 physical
|
|
* - A good lump of memory mapped 0:0 by UTLB entry 0
|
|
* - MSR:IS & MSR:DS set to 0
|
|
*
|
|
* Note that some of the above requirements will be relaxed in the future
|
|
* as the kernel becomes smarter at dealing with different initial conditions
|
|
* but for now you have to be careful
|
|
*/
|
|
_GLOBAL(start_initialization_book3e)
|
|
mflr r28
|
|
|
|
/* First, we need to setup some initial TLBs to map the kernel
|
|
* text, data and bss at PAGE_OFFSET. We don't have a real mode
|
|
* and always use AS 0, so we just set it up to match our link
|
|
* address and never use 0 based addresses.
|
|
*/
|
|
bl initial_tlb_book3e
|
|
|
|
/* Init global core bits */
|
|
bl init_core_book3e
|
|
|
|
/* Init per-thread bits */
|
|
bl init_thread_book3e
|
|
|
|
/* Return to common init code */
|
|
tovirt(r28,r28)
|
|
mtlr r28
|
|
blr
|
|
|
|
|
|
/*
|
|
* Secondary core/processor entry
|
|
*
|
|
* This is entered for thread 0 of a secondary core, all other threads
|
|
* are expected to be stopped. It's similar to start_initialization_book3e
|
|
* except that it's generally entered from the holding loop in head_64.S
|
|
* after CPUs have been gathered by Open Firmware.
|
|
*
|
|
* We assume we are in 32 bits mode running with whatever TLB entry was
|
|
* set for us by the firmware or POR engine.
|
|
*/
|
|
_GLOBAL(book3e_secondary_core_init_tlb_set)
|
|
li r4,1
|
|
b generic_secondary_smp_init
|
|
|
|
_GLOBAL(book3e_secondary_core_init)
|
|
mflr r28
|
|
|
|
/* Do we need to setup initial TLB entry ? */
|
|
cmplwi r4,0
|
|
bne 2f
|
|
|
|
/* Setup TLB for this core */
|
|
bl initial_tlb_book3e
|
|
|
|
/* We can return from the above running at a different
|
|
* address, so recalculate r2 (TOC)
|
|
*/
|
|
bl relative_toc
|
|
|
|
/* Init global core bits */
|
|
2: bl init_core_book3e
|
|
|
|
/* Init per-thread bits */
|
|
3: bl init_thread_book3e
|
|
|
|
/* Return to common init code at proper virtual address.
|
|
*
|
|
* Due to various previous assumptions, we know we entered this
|
|
* function at either the final PAGE_OFFSET mapping or using a
|
|
* 1:1 mapping at 0, so we don't bother doing a complicated check
|
|
* here, we just ensure the return address has the right top bits.
|
|
*
|
|
* Note that if we ever want to be smarter about where we can be
|
|
* started from, we have to be careful that by the time we reach
|
|
* the code below we may already be running at a different location
|
|
* than the one we were called from since initial_tlb_book3e can
|
|
* have moved us already.
|
|
*/
|
|
cmpdi cr0,r28,0
|
|
blt 1f
|
|
lis r3,PAGE_OFFSET@highest
|
|
sldi r3,r3,32
|
|
or r28,r28,r3
|
|
1: mtlr r28
|
|
blr
|
|
|
|
_GLOBAL(book3e_secondary_thread_init)
|
|
mflr r28
|
|
b 3b
|
|
|
|
.globl init_core_book3e
|
|
init_core_book3e:
|
|
/* Establish the interrupt vector base */
|
|
tovirt(r2,r2)
|
|
LOAD_REG_ADDR(r3, interrupt_base_book3e)
|
|
mtspr SPRN_IVPR,r3
|
|
sync
|
|
blr
|
|
|
|
init_thread_book3e:
|
|
lis r3,(SPRN_EPCR_ICM | SPRN_EPCR_GICM)@h
|
|
mtspr SPRN_EPCR,r3
|
|
|
|
/* Make sure interrupts are off */
|
|
wrteei 0
|
|
|
|
/* disable all timers and clear out status */
|
|
li r3,0
|
|
mtspr SPRN_TCR,r3
|
|
mfspr r3,SPRN_TSR
|
|
mtspr SPRN_TSR,r3
|
|
|
|
blr
|
|
|
|
_GLOBAL(__setup_base_ivors)
|
|
SET_IVOR(0, 0x020) /* Critical Input */
|
|
SET_IVOR(1, 0x000) /* Machine Check */
|
|
SET_IVOR(2, 0x060) /* Data Storage */
|
|
SET_IVOR(3, 0x080) /* Instruction Storage */
|
|
SET_IVOR(4, 0x0a0) /* External Input */
|
|
SET_IVOR(5, 0x0c0) /* Alignment */
|
|
SET_IVOR(6, 0x0e0) /* Program */
|
|
SET_IVOR(7, 0x100) /* FP Unavailable */
|
|
SET_IVOR(8, 0x120) /* System Call */
|
|
SET_IVOR(9, 0x140) /* Auxiliary Processor Unavailable */
|
|
SET_IVOR(10, 0x160) /* Decrementer */
|
|
SET_IVOR(11, 0x180) /* Fixed Interval Timer */
|
|
SET_IVOR(12, 0x1a0) /* Watchdog Timer */
|
|
SET_IVOR(13, 0x1c0) /* Data TLB Error */
|
|
SET_IVOR(14, 0x1e0) /* Instruction TLB Error */
|
|
SET_IVOR(15, 0x040) /* Debug */
|
|
|
|
sync
|
|
|
|
blr
|
|
|
|
_GLOBAL(setup_altivec_ivors)
|
|
SET_IVOR(32, 0x200) /* AltiVec Unavailable */
|
|
SET_IVOR(33, 0x220) /* AltiVec Assist */
|
|
blr
|
|
|
|
_GLOBAL(setup_perfmon_ivor)
|
|
SET_IVOR(35, 0x260) /* Performance Monitor */
|
|
blr
|
|
|
|
_GLOBAL(setup_doorbell_ivors)
|
|
SET_IVOR(36, 0x280) /* Processor Doorbell */
|
|
SET_IVOR(37, 0x2a0) /* Processor Doorbell Crit */
|
|
blr
|
|
|
|
_GLOBAL(setup_ehv_ivors)
|
|
SET_IVOR(40, 0x300) /* Embedded Hypervisor System Call */
|
|
SET_IVOR(41, 0x320) /* Embedded Hypervisor Privilege */
|
|
SET_IVOR(38, 0x2c0) /* Guest Processor Doorbell */
|
|
SET_IVOR(39, 0x2e0) /* Guest Processor Doorbell Crit/MC */
|
|
blr
|
|
|
|
_GLOBAL(setup_lrat_ivor)
|
|
SET_IVOR(42, 0x340) /* LRAT Error */
|
|
blr
|