/*
* Copyright 2011 Tilera Corporation. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for
* more details.
*
* Linux interrupt vectors.
*/
#include <linux/linkage.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <asm/ptrace.h>
#include <asm/thread_info.h>
#include <asm/irqflags.h>
#include <asm/asm-offsets.h>
#include <asm/types.h>
#include <asm/signal.h>
#include <hv/hypervisor.h>
#include <arch/abi.h>
#include <arch/interrupts.h>
#include <arch/spr_def.h>
#ifdef CONFIG_PREEMPT
# error "No support for kernel preemption currently"
#endif
#define PTREGS_PTR(reg, ptreg) addli reg, sp, C_ABI_SAVE_AREA_SIZE + (ptreg)
#define PTREGS_OFFSET_SYSCALL PTREGS_OFFSET_REG(TREG_SYSCALL_NR)
.macro push_reg reg, ptr=sp, delta=-8
{
st \ptr, \reg
addli \ptr, \ptr, \delta
}
.endm
.macro pop_reg reg, ptr=sp, delta=8
{
ld \reg, \ptr
addli \ptr, \ptr, \delta
}
.endm
.macro pop_reg_zero reg, zreg, ptr=sp, delta=8
{
move \zreg, zero
ld \reg, \ptr
addi \ptr, \ptr, \delta
}
.endm
.macro push_extra_callee_saves reg
PTREGS_PTR(\reg, PTREGS_OFFSET_REG(51))
push_reg r51, \reg
push_reg r50, \reg
push_reg r49, \reg
push_reg r48, \reg
push_reg r47, \reg
push_reg r46, \reg
push_reg r45, \reg
push_reg r44, \reg
push_reg r43, \reg
push_reg r42, \reg
push_reg r41, \reg
push_reg r40, \reg
push_reg r39, \reg
push_reg r38, \reg
push_reg r37, \reg
push_reg r36, \reg
push_reg r35, \reg
push_reg r34, \reg, PTREGS_OFFSET_BASE - PTREGS_OFFSET_REG(34)
.endm
.macro panic str
.pushsection .rodata, "a"
1:
.asciz "\str"
.popsection
{
moveli r0, hw2_last(1b)
}
{
shl16insli r0, r0, hw1(1b)
}
{
shl16insli r0, r0, hw0(1b)
jal panic
}
.endm
#ifdef __COLLECT_LINKER_FEEDBACK__
.pushsection .text.intvec_feedback,"ax"
intvec_feedback:
.popsection
#endif
/*
* Default interrupt handler.
*
* vecnum is where we'll put this code.
* c_routine is the C routine we'll call.
*
* The C routine is passed two arguments:
* - A pointer to the pt_regs state.
* - The interrupt vector number.
*
* The "processing" argument specifies the code for processing
* the interrupt. Defaults to "handle_interrupt".
*/
.macro int_hand vecnum, vecname, c_routine, processing=handle_interrupt
.org (\vecnum << 8)
intvec_\vecname:
/* Temporarily save a register so we have somewhere to work. */
mtspr SPR_SYSTEM_SAVE_K_1, r0
mfspr r0, SPR_EX_CONTEXT_K_1
andi r0, r0, SPR_EX_CONTEXT_1_1__PL_MASK /* mask off ICS */
.ifc \vecnum, INT_DOUBLE_FAULT
/*
* For double-faults from user-space, fall through to the normal
* register save and stack setup path. Otherwise, it's the
* hypervisor giving us one last chance to dump diagnostics, and we
* branch to the kernel_double_fault routine to do so.
*/
beqz r0, 1f
j _kernel_double_fault
1:
.else
/*
* If we're coming from user-space, then set sp to the top of
* the kernel stack. Otherwise, assume sp is already valid.
*/
{
bnez r0, 0f
move r0, sp
}
.endif
.ifc \c_routine, do_page_fault
/*
* The page_fault handler may be downcalled directly by the
* hypervisor even when Linux is running and has ICS set.
*
* In this case the contents of EX_CONTEXT_K_1 reflect the
* previous fault and can't be relied on to choose whether or
* not to reinitialize the stack pointer. So we add a test
* to see whether SYSTEM_SAVE_K_2 has the high bit set,
* and if so we don't reinitialize sp, since we must be coming
* from Linux. (In fact the precise case is !(val & ~1),
* but any Linux PC has to have the high bit set.)
*
* Note that the hypervisor *always* sets SYSTEM_SAVE_K_2 for
* any path that turns into a downcall to one of our TLB handlers.
*
* FIXME: if we end up never using this path, perhaps we should
* prevent the hypervisor from generating downcalls in this case.
* The advantage of getting a downcall is we can panic in Linux.
*/
mfspr r0, SPR_SYSTEM_SAVE_K_2
{
bltz r0, 0f /* high bit in S_S_1_2 is for a PC to use */
move r0, sp
}
.endif
/*
* SYSTEM_SAVE_K_0 holds the cpu number in the low bits, and
* the current stack top in the higher bits. So we recover
* our stack top by just masking off the low bits, then
* point sp at the top aligned address on the actual stack page.
*/
mfspr r0, SPR_SYSTEM_SAVE_K_0
mm r0, zero, LOG2_THREAD_SIZE, 63
0:
/*
* Align the stack mod 64 so we can properly predict what
* cache lines we need to write-hint to reduce memory fetch
* latency as we enter the kernel. The layout of memory is
* as follows, with cache line 0 at the lowest VA, and cache
* line 8 just below the r0 value this "andi" computes.
* Note that we never write to cache line 8, and we skip
* cache lines 1-3 for syscalls.
*
* cache line 8: ptregs padding (two words)
* cache line 7: sp, lr, pc, ex1, faultnum, orig_r0, flags, cmpexch
* cache line 6: r46...r53 (tp)
* cache line 5: r38...r45
* cache line 4: r30...r37
* cache line 3: r22...r29
* cache line 2: r14...r21
* cache line 1: r6...r13
* cache line 0: 2 x frame, r0..r5
*/
andi r0, r0, -64
/*
* Push the first four registers on the stack, so that we can set
* them to vector-unique values before we jump to the common code.
*
* Registers are pushed on the stack as a struct pt_regs,
* with the sp initially just above the struct, and when we're
* done, sp points to the base of the struct, minus
* C_ABI_SAVE_AREA_SIZE, so we can directly jal to C code.
*
* This routine saves just the first four registers, plus the
* stack context so we can do proper backtracing right away,
* and defers to handle_interrupt to save the rest.
* The backtracer needs pc, ex1, lr, sp, r52, and faultnum,
* and needs sp set to its final location at the bottom of
* the stack frame.
*/
addli r0, r0, PTREGS_OFFSET_LR - (PTREGS_SIZE + KSTK_PTREGS_GAP)
wh64 r0 /* cache line 7 */
{
st r0, lr
addli r0, r0, PTREGS_OFFSET_SP - PTREGS_OFFSET_LR
}
{
st r0, sp
addli sp, r0, PTREGS_OFFSET_REG(52) - PTREGS_OFFSET_SP
}
wh64 sp /* cache line 6 */
{
st sp
|