mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
39
arch/xtensa/kernel/Makefile
Normal file
39
arch/xtensa/kernel/Makefile
Normal file
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# Makefile for the Linux/Xtensa kernel.
|
||||
#
|
||||
|
||||
extra-y := head.o vmlinux.lds
|
||||
|
||||
obj-y := align.o coprocessor.o entry.o irq.o pci-dma.o platform.o process.o \
|
||||
ptrace.o setup.o signal.o stacktrace.o syscall.o time.o traps.o \
|
||||
vectors.o
|
||||
|
||||
obj-$(CONFIG_KGDB) += xtensa-stub.o
|
||||
obj-$(CONFIG_PCI) += pci.o
|
||||
obj-$(CONFIG_MODULES) += xtensa_ksyms.o module.o
|
||||
obj-$(CONFIG_FUNCTION_TRACER) += mcount.o
|
||||
obj-$(CONFIG_SMP) += smp.o mxhead.o
|
||||
|
||||
AFLAGS_head.o += -mtext-section-literals
|
||||
|
||||
# In the Xtensa architecture, assembly generates literals which must always
|
||||
# precede the L32R instruction with a relative offset less than 256 kB.
|
||||
# Therefore, the .text and .literal section must be combined in parenthesis
|
||||
# in the linker script, such as: *(.literal .text).
|
||||
#
|
||||
# We need to post-process the generated vmlinux.lds scripts to convert
|
||||
# *(xxx.text) to *(xxx.literal xxx.text) for the following text sections:
|
||||
# .text .ref.text .*init.text .*exit.text .text.*
|
||||
#
|
||||
# Replicate rules in scripts/Makefile.build
|
||||
|
||||
sed-y = -e 's/\*(\(\.[a-z]*it\|\.ref\|\)\.text)/*(\1.literal \1.text)/g' \
|
||||
-e 's/\.text\.unlikely/.literal.unlikely .text.unlikely/g' \
|
||||
-e 's/\*(\(\.text\.[a-z]*\))/*(\1.literal \1)/g'
|
||||
|
||||
quiet_cmd__cpp_lds_S = LDS $@
|
||||
cmd__cpp_lds_S = $(CPP) $(cpp_flags) -P -C -Uxtensa -D__ASSEMBLY__ $< \
|
||||
| sed $(sed-y) >$@
|
||||
|
||||
$(obj)/vmlinux.lds: $(src)/vmlinux.lds.S FORCE
|
||||
$(call if_changed_dep,_cpp_lds_S)
|
488
arch/xtensa/kernel/align.S
Normal file
488
arch/xtensa/kernel/align.S
Normal file
|
@ -0,0 +1,488 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/align.S
|
||||
*
|
||||
* Handle unalignment exceptions in kernel space.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General
|
||||
* Public License. See the file "COPYING" in the main directory of
|
||||
* this archive for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2005 Tensilica, Inc.
|
||||
* Copyright (C) 2014 Cadence Design Systems Inc.
|
||||
*
|
||||
* Rewritten by Chris Zankel <chris@zankel.net>
|
||||
*
|
||||
* Based on work from Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
* and Marc Gauthier <marc@tensilica.com, marc@alimni.uwaterloo.ca>
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/current.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
#include <asm/processor.h>
|
||||
|
||||
#if XCHAL_UNALIGNED_LOAD_EXCEPTION || XCHAL_UNALIGNED_STORE_EXCEPTION
|
||||
|
||||
/* First-level exception handler for unaligned exceptions.
|
||||
*
|
||||
* Note: This handler works only for kernel exceptions. Unaligned user
|
||||
* access should get a seg fault.
|
||||
*/
|
||||
|
||||
/* Big and little endian 16-bit values are located in
|
||||
* different halves of a register. HWORD_START helps to
|
||||
* abstract the notion of extracting a 16-bit value from a
|
||||
* register.
|
||||
* We also have to define new shifting instructions because
|
||||
* lsb and msb are on 'opposite' ends in a register for
|
||||
* different endian machines.
|
||||
*
|
||||
* Assume a memory region in ascending address:
|
||||
* 0 1 2 3|4 5 6 7
|
||||
*
|
||||
* When loading one word into a register, the content of that register is:
|
||||
* LE 3 2 1 0, 7 6 5 4
|
||||
* BE 0 1 2 3, 4 5 6 7
|
||||
*
|
||||
* Masking the bits of the higher/lower address means:
|
||||
* LE X X 0 0, 0 0 X X
|
||||
* BE 0 0 X X, X X 0 0
|
||||
*
|
||||
* Shifting to higher/lower addresses, means:
|
||||
* LE shift left / shift right
|
||||
* BE shift right / shift left
|
||||
*
|
||||
* Extracting 16 bits from a 32 bit reg. value to higher/lower address means:
|
||||
* LE mask 0 0 X X / shift left
|
||||
* BE shift left / mask 0 0 X X
|
||||
*/
|
||||
|
||||
#define UNALIGNED_USER_EXCEPTION
|
||||
|
||||
#if XCHAL_HAVE_BE
|
||||
|
||||
#define HWORD_START 16
|
||||
#define INSN_OP0 28
|
||||
#define INSN_T 24
|
||||
#define INSN_OP1 16
|
||||
|
||||
.macro __src_b r, w0, w1; src \r, \w0, \w1; .endm
|
||||
.macro __ssa8 r; ssa8b \r; .endm
|
||||
.macro __ssa8r r; ssa8l \r; .endm
|
||||
.macro __sh r, s; srl \r, \s; .endm
|
||||
.macro __sl r, s; sll \r, \s; .endm
|
||||
.macro __exth r, s; extui \r, \s, 0, 16; .endm
|
||||
.macro __extl r, s; slli \r, \s, 16; .endm
|
||||
|
||||
#else
|
||||
|
||||
#define HWORD_START 0
|
||||
#define INSN_OP0 0
|
||||
#define INSN_T 4
|
||||
#define INSN_OP1 12
|
||||
|
||||
.macro __src_b r, w0, w1; src \r, \w1, \w0; .endm
|
||||
.macro __ssa8 r; ssa8l \r; .endm
|
||||
.macro __ssa8r r; ssa8b \r; .endm
|
||||
.macro __sh r, s; sll \r, \s; .endm
|
||||
.macro __sl r, s; srl \r, \s; .endm
|
||||
.macro __exth r, s; slli \r, \s, 16; .endm
|
||||
.macro __extl r, s; extui \r, \s, 0, 16; .endm
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* xxxx xxxx = imm8 field
|
||||
* yyyy = imm4 field
|
||||
* ssss = s field
|
||||
* tttt = t field
|
||||
*
|
||||
* 16 0
|
||||
* -------------------
|
||||
* L32I.N yyyy ssss tttt 1000
|
||||
* S32I.N yyyy ssss tttt 1001
|
||||
*
|
||||
* 23 0
|
||||
* -----------------------------
|
||||
* res 0000 0010
|
||||
* L16UI xxxx xxxx 0001 ssss tttt 0010
|
||||
* L32I xxxx xxxx 0010 ssss tttt 0010
|
||||
* XXX 0011 ssss tttt 0010
|
||||
* XXX 0100 ssss tttt 0010
|
||||
* S16I xxxx xxxx 0101 ssss tttt 0010
|
||||
* S32I xxxx xxxx 0110 ssss tttt 0010
|
||||
* XXX 0111 ssss tttt 0010
|
||||
* XXX 1000 ssss tttt 0010
|
||||
* L16SI xxxx xxxx 1001 ssss tttt 0010
|
||||
* XXX 1010 0010
|
||||
* **L32AI xxxx xxxx 1011 ssss tttt 0010 unsupported
|
||||
* XXX 1100 0010
|
||||
* XXX 1101 0010
|
||||
* XXX 1110 0010
|
||||
* **S32RI xxxx xxxx 1111 ssss tttt 0010 unsupported
|
||||
* -----------------------------
|
||||
* ^ ^ ^
|
||||
* sub-opcode (NIBBLE_R) -+ | |
|
||||
* t field (NIBBLE_T) -----------+ |
|
||||
* major opcode (NIBBLE_OP0) --------------+
|
||||
*/
|
||||
|
||||
#define OP0_L32I_N 0x8 /* load immediate narrow */
|
||||
#define OP0_S32I_N 0x9 /* store immediate narrow */
|
||||
#define OP1_SI_MASK 0x4 /* OP1 bit set for stores */
|
||||
#define OP1_SI_BIT 2 /* OP1 bit number for stores */
|
||||
|
||||
#define OP1_L32I 0x2
|
||||
#define OP1_L16UI 0x1
|
||||
#define OP1_L16SI 0x9
|
||||
#define OP1_L32AI 0xb
|
||||
|
||||
#define OP1_S32I 0x6
|
||||
#define OP1_S16I 0x5
|
||||
#define OP1_S32RI 0xf
|
||||
|
||||
/*
|
||||
* Entry condition:
|
||||
*
|
||||
* a0: trashed, original value saved on stack (PT_AREG0)
|
||||
* a1: a1
|
||||
* a2: new stack pointer, original in DEPC
|
||||
* a3: a3
|
||||
* depc: a2, original value saved on stack (PT_DEPC)
|
||||
* excsave_1: dispatch table
|
||||
*
|
||||
* PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC
|
||||
* < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception
|
||||
*/
|
||||
|
||||
|
||||
ENTRY(fast_unaligned)
|
||||
|
||||
/* Note: We don't expect the address to be aligned on a word
|
||||
* boundary. After all, the processor generated that exception
|
||||
* and it would be a hardware fault.
|
||||
*/
|
||||
|
||||
/* Save some working register */
|
||||
|
||||
s32i a4, a2, PT_AREG4
|
||||
s32i a5, a2, PT_AREG5
|
||||
s32i a6, a2, PT_AREG6
|
||||
s32i a7, a2, PT_AREG7
|
||||
s32i a8, a2, PT_AREG8
|
||||
|
||||
rsr a0, depc
|
||||
s32i a0, a2, PT_AREG2
|
||||
s32i a3, a2, PT_AREG3
|
||||
|
||||
rsr a3, excsave1
|
||||
movi a4, fast_unaligned_fixup
|
||||
s32i a4, a3, EXC_TABLE_FIXUP
|
||||
|
||||
/* Keep value of SAR in a0 */
|
||||
|
||||
rsr a0, sar
|
||||
rsr a8, excvaddr # load unaligned memory address
|
||||
|
||||
/* Now, identify one of the following load/store instructions.
|
||||
*
|
||||
* The only possible danger of a double exception on the
|
||||
* following l32i instructions is kernel code in vmalloc
|
||||
* memory. The processor was just executing at the EPC_1
|
||||
* address, and indeed, already fetched the instruction. That
|
||||
* guarantees a TLB mapping, which hasn't been replaced by
|
||||
* this unaligned exception handler that uses only static TLB
|
||||
* mappings. However, high-level interrupt handlers might
|
||||
* modify TLB entries, so for the generic case, we register a
|
||||
* TABLE_FIXUP handler here, too.
|
||||
*/
|
||||
|
||||
/* a3...a6 saved on stack, a2 = SP */
|
||||
|
||||
/* Extract the instruction that caused the unaligned access. */
|
||||
|
||||
rsr a7, epc1 # load exception address
|
||||
movi a3, ~3
|
||||
and a3, a3, a7 # mask lower bits
|
||||
|
||||
l32i a4, a3, 0 # load 2 words
|
||||
l32i a5, a3, 4
|
||||
|
||||
__ssa8 a7
|
||||
__src_b a4, a4, a5 # a4 has the instruction
|
||||
|
||||
/* Analyze the instruction (load or store?). */
|
||||
|
||||
extui a5, a4, INSN_OP0, 4 # get insn.op0 nibble
|
||||
|
||||
#if XCHAL_HAVE_DENSITY
|
||||
_beqi a5, OP0_L32I_N, .Lload # L32I.N, jump
|
||||
addi a6, a5, -OP0_S32I_N
|
||||
_beqz a6, .Lstore # S32I.N, do a store
|
||||
#endif
|
||||
/* 'store indicator bit' not set, jump */
|
||||
_bbci.l a4, OP1_SI_BIT + INSN_OP1, .Lload
|
||||
|
||||
/* Store: Jump to table entry to get the value in the source register.*/
|
||||
|
||||
.Lstore:movi a5, .Lstore_table # table
|
||||
extui a6, a4, INSN_T, 4 # get source register
|
||||
addx8 a5, a6, a5
|
||||
jx a5 # jump into table
|
||||
|
||||
/* Load: Load memory address. */
|
||||
|
||||
.Lload: movi a3, ~3
|
||||
and a3, a3, a8 # align memory address
|
||||
|
||||
__ssa8 a8
|
||||
#ifdef UNALIGNED_USER_EXCEPTION
|
||||
addi a3, a3, 8
|
||||
l32e a5, a3, -8
|
||||
l32e a6, a3, -4
|
||||
#else
|
||||
l32i a5, a3, 0
|
||||
l32i a6, a3, 4
|
||||
#endif
|
||||
__src_b a3, a5, a6 # a3 has the data word
|
||||
|
||||
#if XCHAL_HAVE_DENSITY
|
||||
addi a7, a7, 2 # increment PC (assume 16-bit insn)
|
||||
|
||||
extui a5, a4, INSN_OP0, 4
|
||||
_beqi a5, OP0_L32I_N, 1f # l32i.n: jump
|
||||
|
||||
addi a7, a7, 1
|
||||
#else
|
||||
addi a7, a7, 3
|
||||
#endif
|
||||
|
||||
extui a5, a4, INSN_OP1, 4
|
||||
_beqi a5, OP1_L32I, 1f # l32i: jump
|
||||
|
||||
extui a3, a3, 0, 16 # extract lower 16 bits
|
||||
_beqi a5, OP1_L16UI, 1f
|
||||
addi a5, a5, -OP1_L16SI
|
||||
_bnez a5, .Linvalid_instruction_load
|
||||
|
||||
/* sign extend value */
|
||||
|
||||
slli a3, a3, 16
|
||||
srai a3, a3, 16
|
||||
|
||||
/* Set target register. */
|
||||
|
||||
1:
|
||||
extui a4, a4, INSN_T, 4 # extract target register
|
||||
movi a5, .Lload_table
|
||||
addx8 a4, a4, a5
|
||||
jx a4 # jump to entry for target register
|
||||
|
||||
.align 8
|
||||
.Lload_table:
|
||||
s32i a3, a2, PT_AREG0; _j .Lexit; .align 8
|
||||
mov a1, a3; _j .Lexit; .align 8 # fishy??
|
||||
s32i a3, a2, PT_AREG2; _j .Lexit; .align 8
|
||||
s32i a3, a2, PT_AREG3; _j .Lexit; .align 8
|
||||
s32i a3, a2, PT_AREG4; _j .Lexit; .align 8
|
||||
s32i a3, a2, PT_AREG5; _j .Lexit; .align 8
|
||||
s32i a3, a2, PT_AREG6; _j .Lexit; .align 8
|
||||
s32i a3, a2, PT_AREG7; _j .Lexit; .align 8
|
||||
s32i a3, a2, PT_AREG8; _j .Lexit; .align 8
|
||||
mov a9, a3 ; _j .Lexit; .align 8
|
||||
mov a10, a3 ; _j .Lexit; .align 8
|
||||
mov a11, a3 ; _j .Lexit; .align 8
|
||||
mov a12, a3 ; _j .Lexit; .align 8
|
||||
mov a13, a3 ; _j .Lexit; .align 8
|
||||
mov a14, a3 ; _j .Lexit; .align 8
|
||||
mov a15, a3 ; _j .Lexit; .align 8
|
||||
|
||||
.Lstore_table:
|
||||
l32i a3, a2, PT_AREG0; _j 1f; .align 8
|
||||
mov a3, a1; _j 1f; .align 8 # fishy??
|
||||
l32i a3, a2, PT_AREG2; _j 1f; .align 8
|
||||
l32i a3, a2, PT_AREG3; _j 1f; .align 8
|
||||
l32i a3, a2, PT_AREG4; _j 1f; .align 8
|
||||
l32i a3, a2, PT_AREG5; _j 1f; .align 8
|
||||
l32i a3, a2, PT_AREG6; _j 1f; .align 8
|
||||
l32i a3, a2, PT_AREG7; _j 1f; .align 8
|
||||
l32i a3, a2, PT_AREG8; _j 1f; .align 8
|
||||
mov a3, a9 ; _j 1f; .align 8
|
||||
mov a3, a10 ; _j 1f; .align 8
|
||||
mov a3, a11 ; _j 1f; .align 8
|
||||
mov a3, a12 ; _j 1f; .align 8
|
||||
mov a3, a13 ; _j 1f; .align 8
|
||||
mov a3, a14 ; _j 1f; .align 8
|
||||
mov a3, a15 ; _j 1f; .align 8
|
||||
|
||||
/* We cannot handle this exception. */
|
||||
|
||||
.extern _kernel_exception
|
||||
.Linvalid_instruction_load:
|
||||
.Linvalid_instruction_store:
|
||||
|
||||
movi a4, 0
|
||||
rsr a3, excsave1
|
||||
s32i a4, a3, EXC_TABLE_FIXUP
|
||||
|
||||
/* Restore a4...a8 and SAR, set SP, and jump to default exception. */
|
||||
|
||||
l32i a8, a2, PT_AREG8
|
||||
l32i a7, a2, PT_AREG7
|
||||
l32i a6, a2, PT_AREG6
|
||||
l32i a5, a2, PT_AREG5
|
||||
l32i a4, a2, PT_AREG4
|
||||
wsr a0, sar
|
||||
mov a1, a2
|
||||
|
||||
rsr a0, ps
|
||||
bbsi.l a0, PS_UM_BIT, 2f # jump if user mode
|
||||
|
||||
movi a0, _kernel_exception
|
||||
jx a0
|
||||
|
||||
2: movi a0, _user_exception
|
||||
jx a0
|
||||
|
||||
1: # a7: instruction pointer, a4: instruction, a3: value
|
||||
|
||||
movi a6, 0 # mask: ffffffff:00000000
|
||||
|
||||
#if XCHAL_HAVE_DENSITY
|
||||
addi a7, a7, 2 # incr. PC,assume 16-bit instruction
|
||||
|
||||
extui a5, a4, INSN_OP0, 4 # extract OP0
|
||||
addi a5, a5, -OP0_S32I_N
|
||||
_beqz a5, 1f # s32i.n: jump
|
||||
|
||||
addi a7, a7, 1 # increment PC, 32-bit instruction
|
||||
#else
|
||||
addi a7, a7, 3 # increment PC, 32-bit instruction
|
||||
#endif
|
||||
|
||||
extui a5, a4, INSN_OP1, 4 # extract OP1
|
||||
_beqi a5, OP1_S32I, 1f # jump if 32 bit store
|
||||
_bnei a5, OP1_S16I, .Linvalid_instruction_store
|
||||
|
||||
movi a5, -1
|
||||
__extl a3, a3 # get 16-bit value
|
||||
__exth a6, a5 # get 16-bit mask ffffffff:ffff0000
|
||||
|
||||
/* Get memory address */
|
||||
|
||||
1:
|
||||
movi a4, ~3
|
||||
and a4, a4, a8 # align memory address
|
||||
|
||||
/* Insert value into memory */
|
||||
|
||||
movi a5, -1 # mask: ffffffff:XXXX0000
|
||||
#ifdef UNALIGNED_USER_EXCEPTION
|
||||
addi a4, a4, 8
|
||||
#endif
|
||||
|
||||
__ssa8r a8
|
||||
__src_b a8, a5, a6 # lo-mask F..F0..0 (BE) 0..0F..F (LE)
|
||||
__src_b a6, a6, a5 # hi-mask 0..0F..F (BE) F..F0..0 (LE)
|
||||
#ifdef UNALIGNED_USER_EXCEPTION
|
||||
l32e a5, a4, -8
|
||||
#else
|
||||
l32i a5, a4, 0 # load lower address word
|
||||
#endif
|
||||
and a5, a5, a8 # mask
|
||||
__sh a8, a3 # shift value
|
||||
or a5, a5, a8 # or with original value
|
||||
#ifdef UNALIGNED_USER_EXCEPTION
|
||||
s32e a5, a4, -8
|
||||
l32e a8, a4, -4
|
||||
#else
|
||||
s32i a5, a4, 0 # store
|
||||
l32i a8, a4, 4 # same for upper address word
|
||||
#endif
|
||||
__sl a5, a3
|
||||
and a6, a8, a6
|
||||
or a6, a6, a5
|
||||
#ifdef UNALIGNED_USER_EXCEPTION
|
||||
s32e a6, a4, -4
|
||||
#else
|
||||
s32i a6, a4, 4
|
||||
#endif
|
||||
|
||||
.Lexit:
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
rsr a4, lend # check if we reached LEND
|
||||
bne a7, a4, 1f
|
||||
rsr a4, lcount # and LCOUNT != 0
|
||||
beqz a4, 1f
|
||||
addi a4, a4, -1 # decrement LCOUNT and set
|
||||
rsr a7, lbeg # set PC to LBEGIN
|
||||
wsr a4, lcount
|
||||
#endif
|
||||
|
||||
1: wsr a7, epc1 # skip emulated instruction
|
||||
|
||||
/* Update icount if we're single-stepping in userspace. */
|
||||
rsr a4, icountlevel
|
||||
beqz a4, 1f
|
||||
bgeui a4, LOCKLEVEL + 1, 1f
|
||||
rsr a4, icount
|
||||
addi a4, a4, 1
|
||||
wsr a4, icount
|
||||
1:
|
||||
movi a4, 0
|
||||
rsr a3, excsave1
|
||||
s32i a4, a3, EXC_TABLE_FIXUP
|
||||
|
||||
/* Restore working register */
|
||||
|
||||
l32i a8, a2, PT_AREG8
|
||||
l32i a7, a2, PT_AREG7
|
||||
l32i a6, a2, PT_AREG6
|
||||
l32i a5, a2, PT_AREG5
|
||||
l32i a4, a2, PT_AREG4
|
||||
l32i a3, a2, PT_AREG3
|
||||
|
||||
/* restore SAR and return */
|
||||
|
||||
wsr a0, sar
|
||||
l32i a0, a2, PT_AREG0
|
||||
l32i a2, a2, PT_AREG2
|
||||
rfe
|
||||
|
||||
ENDPROC(fast_unaligned)
|
||||
|
||||
ENTRY(fast_unaligned_fixup)
|
||||
|
||||
l32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
wsr a3, excsave1
|
||||
|
||||
l32i a8, a2, PT_AREG8
|
||||
l32i a7, a2, PT_AREG7
|
||||
l32i a6, a2, PT_AREG6
|
||||
l32i a5, a2, PT_AREG5
|
||||
l32i a4, a2, PT_AREG4
|
||||
l32i a0, a2, PT_AREG2
|
||||
xsr a0, depc # restore depc and a0
|
||||
wsr a0, sar
|
||||
|
||||
rsr a0, exccause
|
||||
s32i a0, a2, PT_DEPC # mark as a regular exception
|
||||
|
||||
rsr a0, ps
|
||||
bbsi.l a0, PS_UM_BIT, 1f # jump if user mode
|
||||
|
||||
rsr a0, exccause
|
||||
addx4 a0, a0, a3 # find entry in table
|
||||
l32i a0, a0, EXC_TABLE_FAST_KERNEL # load handler
|
||||
l32i a3, a2, PT_AREG3
|
||||
jx a0
|
||||
1:
|
||||
rsr a0, exccause
|
||||
addx4 a0, a0, a3 # find entry in table
|
||||
l32i a0, a0, EXC_TABLE_FAST_USER # load handler
|
||||
l32i a3, a2, PT_AREG3
|
||||
jx a0
|
||||
|
||||
ENDPROC(fast_unaligned_fixup)
|
||||
|
||||
#endif /* XCHAL_UNALIGNED_LOAD_EXCEPTION || XCHAL_UNALIGNED_STORE_EXCEPTION */
|
113
arch/xtensa/kernel/asm-offsets.c
Normal file
113
arch/xtensa/kernel/asm-offsets.c
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/asm-offsets.c
|
||||
*
|
||||
* Generates definitions from c-type structures used by assembly sources.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2005 Tensilica Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
*/
|
||||
|
||||
#include <asm/processor.h>
|
||||
#include <asm/coprocessor.h>
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/thread_info.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/kbuild.h>
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* struct pt_regs */
|
||||
DEFINE(PT_PC, offsetof (struct pt_regs, pc));
|
||||
DEFINE(PT_PS, offsetof (struct pt_regs, ps));
|
||||
DEFINE(PT_DEPC, offsetof (struct pt_regs, depc));
|
||||
DEFINE(PT_EXCCAUSE, offsetof (struct pt_regs, exccause));
|
||||
DEFINE(PT_EXCVADDR, offsetof (struct pt_regs, excvaddr));
|
||||
DEFINE(PT_DEBUGCAUSE, offsetof (struct pt_regs, debugcause));
|
||||
DEFINE(PT_WMASK, offsetof (struct pt_regs, wmask));
|
||||
DEFINE(PT_LBEG, offsetof (struct pt_regs, lbeg));
|
||||
DEFINE(PT_LEND, offsetof (struct pt_regs, lend));
|
||||
DEFINE(PT_LCOUNT, offsetof (struct pt_regs, lcount));
|
||||
DEFINE(PT_SAR, offsetof (struct pt_regs, sar));
|
||||
DEFINE(PT_ICOUNTLEVEL, offsetof (struct pt_regs, icountlevel));
|
||||
DEFINE(PT_SYSCALL, offsetof (struct pt_regs, syscall));
|
||||
DEFINE(PT_SCOMPARE1, offsetof(struct pt_regs, scompare1));
|
||||
DEFINE(PT_THREADPTR, offsetof(struct pt_regs, threadptr));
|
||||
DEFINE(PT_AREG, offsetof (struct pt_regs, areg[0]));
|
||||
DEFINE(PT_AREG0, offsetof (struct pt_regs, areg[0]));
|
||||
DEFINE(PT_AREG1, offsetof (struct pt_regs, areg[1]));
|
||||
DEFINE(PT_AREG2, offsetof (struct pt_regs, areg[2]));
|
||||
DEFINE(PT_AREG3, offsetof (struct pt_regs, areg[3]));
|
||||
DEFINE(PT_AREG4, offsetof (struct pt_regs, areg[4]));
|
||||
DEFINE(PT_AREG5, offsetof (struct pt_regs, areg[5]));
|
||||
DEFINE(PT_AREG6, offsetof (struct pt_regs, areg[6]));
|
||||
DEFINE(PT_AREG7, offsetof (struct pt_regs, areg[7]));
|
||||
DEFINE(PT_AREG8, offsetof (struct pt_regs, areg[8]));
|
||||
DEFINE(PT_AREG9, offsetof (struct pt_regs, areg[9]));
|
||||
DEFINE(PT_AREG10, offsetof (struct pt_regs, areg[10]));
|
||||
DEFINE(PT_AREG11, offsetof (struct pt_regs, areg[11]));
|
||||
DEFINE(PT_AREG12, offsetof (struct pt_regs, areg[12]));
|
||||
DEFINE(PT_AREG13, offsetof (struct pt_regs, areg[13]));
|
||||
DEFINE(PT_AREG14, offsetof (struct pt_regs, areg[14]));
|
||||
DEFINE(PT_AREG15, offsetof (struct pt_regs, areg[15]));
|
||||
DEFINE(PT_WINDOWBASE, offsetof (struct pt_regs, windowbase));
|
||||
DEFINE(PT_WINDOWSTART, offsetof(struct pt_regs, windowstart));
|
||||
DEFINE(PT_SIZE, sizeof(struct pt_regs));
|
||||
DEFINE(PT_AREG_END, offsetof (struct pt_regs, areg[XCHAL_NUM_AREGS]));
|
||||
DEFINE(PT_USER_SIZE, offsetof(struct pt_regs, areg[XCHAL_NUM_AREGS]));
|
||||
DEFINE(PT_XTREGS_OPT, offsetof(struct pt_regs, xtregs_opt));
|
||||
DEFINE(XTREGS_OPT_SIZE, sizeof(xtregs_opt_t));
|
||||
|
||||
/* struct task_struct */
|
||||
DEFINE(TASK_PTRACE, offsetof (struct task_struct, ptrace));
|
||||
DEFINE(TASK_MM, offsetof (struct task_struct, mm));
|
||||
DEFINE(TASK_ACTIVE_MM, offsetof (struct task_struct, active_mm));
|
||||
DEFINE(TASK_PID, offsetof (struct task_struct, pid));
|
||||
DEFINE(TASK_THREAD, offsetof (struct task_struct, thread));
|
||||
DEFINE(TASK_THREAD_INFO, offsetof (struct task_struct, stack));
|
||||
DEFINE(TASK_STRUCT_SIZE, sizeof (struct task_struct));
|
||||
|
||||
/* struct thread_info (offset from start_struct) */
|
||||
DEFINE(THREAD_RA, offsetof (struct task_struct, thread.ra));
|
||||
DEFINE(THREAD_SP, offsetof (struct task_struct, thread.sp));
|
||||
DEFINE(THREAD_CPENABLE, offsetof (struct thread_info, cpenable));
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
DEFINE(THREAD_XTREGS_CP0, offsetof (struct thread_info, xtregs_cp));
|
||||
DEFINE(THREAD_XTREGS_CP1, offsetof (struct thread_info, xtregs_cp));
|
||||
DEFINE(THREAD_XTREGS_CP2, offsetof (struct thread_info, xtregs_cp));
|
||||
DEFINE(THREAD_XTREGS_CP3, offsetof (struct thread_info, xtregs_cp));
|
||||
DEFINE(THREAD_XTREGS_CP4, offsetof (struct thread_info, xtregs_cp));
|
||||
DEFINE(THREAD_XTREGS_CP5, offsetof (struct thread_info, xtregs_cp));
|
||||
DEFINE(THREAD_XTREGS_CP6, offsetof (struct thread_info, xtregs_cp));
|
||||
DEFINE(THREAD_XTREGS_CP7, offsetof (struct thread_info, xtregs_cp));
|
||||
#endif
|
||||
DEFINE(THREAD_XTREGS_USER, offsetof (struct thread_info, xtregs_user));
|
||||
DEFINE(XTREGS_USER_SIZE, sizeof(xtregs_user_t));
|
||||
DEFINE(THREAD_CURRENT_DS, offsetof (struct task_struct, \
|
||||
thread.current_ds));
|
||||
|
||||
/* struct mm_struct */
|
||||
DEFINE(MM_USERS, offsetof(struct mm_struct, mm_users));
|
||||
DEFINE(MM_PGD, offsetof (struct mm_struct, pgd));
|
||||
DEFINE(MM_CONTEXT, offsetof (struct mm_struct, context));
|
||||
|
||||
/* struct page */
|
||||
DEFINE(PAGE_FLAGS, offsetof(struct page, flags));
|
||||
|
||||
/* constants */
|
||||
DEFINE(_CLONE_VM, CLONE_VM);
|
||||
DEFINE(_CLONE_UNTRACED, CLONE_UNTRACED);
|
||||
DEFINE(PG_ARCH_1, PG_arch_1);
|
||||
|
||||
return 0;
|
||||
}
|
355
arch/xtensa/kernel/coprocessor.S
Normal file
355
arch/xtensa/kernel/coprocessor.S
Normal file
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/coprocessor.S
|
||||
*
|
||||
* Xtensa processor configuration-specific table of coprocessor and
|
||||
* other custom register layout information.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2003 - 2007 Tensilica Inc.
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/coprocessor.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/current.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/signal.h>
|
||||
#include <asm/tlbflush.h>
|
||||
|
||||
/*
|
||||
* Entry condition:
|
||||
*
|
||||
* a0: trashed, original value saved on stack (PT_AREG0)
|
||||
* a1: a1
|
||||
* a2: new stack pointer, original in DEPC
|
||||
* a3: a3
|
||||
* depc: a2, original value saved on stack (PT_DEPC)
|
||||
* excsave_1: dispatch table
|
||||
*
|
||||
* PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC
|
||||
* < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception
|
||||
*/
|
||||
|
||||
/* IO protection is currently unsupported. */
|
||||
|
||||
ENTRY(fast_io_protect)
|
||||
|
||||
wsr a0, excsave1
|
||||
movi a0, unrecoverable_exception
|
||||
callx0 a0
|
||||
|
||||
ENDPROC(fast_io_protect)
|
||||
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
|
||||
/*
|
||||
* Macros for lazy context switch.
|
||||
*/
|
||||
|
||||
#define SAVE_CP_REGS(x) \
|
||||
.align 4; \
|
||||
.Lsave_cp_regs_cp##x: \
|
||||
.if XTENSA_HAVE_COPROCESSOR(x); \
|
||||
xchal_cp##x##_store a2 a4 a5 a6 a7; \
|
||||
.endif; \
|
||||
jx a0
|
||||
|
||||
#define SAVE_CP_REGS_TAB(x) \
|
||||
.if XTENSA_HAVE_COPROCESSOR(x); \
|
||||
.long .Lsave_cp_regs_cp##x - .Lsave_cp_regs_jump_table; \
|
||||
.else; \
|
||||
.long 0; \
|
||||
.endif; \
|
||||
.long THREAD_XTREGS_CP##x
|
||||
|
||||
|
||||
#define LOAD_CP_REGS(x) \
|
||||
.align 4; \
|
||||
.Lload_cp_regs_cp##x: \
|
||||
.if XTENSA_HAVE_COPROCESSOR(x); \
|
||||
xchal_cp##x##_load a2 a4 a5 a6 a7; \
|
||||
.endif; \
|
||||
jx a0
|
||||
|
||||
#define LOAD_CP_REGS_TAB(x) \
|
||||
.if XTENSA_HAVE_COPROCESSOR(x); \
|
||||
.long .Lload_cp_regs_cp##x - .Lload_cp_regs_jump_table; \
|
||||
.else; \
|
||||
.long 0; \
|
||||
.endif; \
|
||||
.long THREAD_XTREGS_CP##x
|
||||
|
||||
SAVE_CP_REGS(0)
|
||||
SAVE_CP_REGS(1)
|
||||
SAVE_CP_REGS(2)
|
||||
SAVE_CP_REGS(3)
|
||||
SAVE_CP_REGS(4)
|
||||
SAVE_CP_REGS(5)
|
||||
SAVE_CP_REGS(6)
|
||||
SAVE_CP_REGS(7)
|
||||
|
||||
LOAD_CP_REGS(0)
|
||||
LOAD_CP_REGS(1)
|
||||
LOAD_CP_REGS(2)
|
||||
LOAD_CP_REGS(3)
|
||||
LOAD_CP_REGS(4)
|
||||
LOAD_CP_REGS(5)
|
||||
LOAD_CP_REGS(6)
|
||||
LOAD_CP_REGS(7)
|
||||
|
||||
.align 4
|
||||
.Lsave_cp_regs_jump_table:
|
||||
SAVE_CP_REGS_TAB(0)
|
||||
SAVE_CP_REGS_TAB(1)
|
||||
SAVE_CP_REGS_TAB(2)
|
||||
SAVE_CP_REGS_TAB(3)
|
||||
SAVE_CP_REGS_TAB(4)
|
||||
SAVE_CP_REGS_TAB(5)
|
||||
SAVE_CP_REGS_TAB(6)
|
||||
SAVE_CP_REGS_TAB(7)
|
||||
|
||||
.Lload_cp_regs_jump_table:
|
||||
LOAD_CP_REGS_TAB(0)
|
||||
LOAD_CP_REGS_TAB(1)
|
||||
LOAD_CP_REGS_TAB(2)
|
||||
LOAD_CP_REGS_TAB(3)
|
||||
LOAD_CP_REGS_TAB(4)
|
||||
LOAD_CP_REGS_TAB(5)
|
||||
LOAD_CP_REGS_TAB(6)
|
||||
LOAD_CP_REGS_TAB(7)
|
||||
|
||||
/*
|
||||
* coprocessor_save(buffer, index)
|
||||
* a2 a3
|
||||
* coprocessor_load(buffer, index)
|
||||
* a2 a3
|
||||
*
|
||||
* Save or load coprocessor registers for coprocessor 'index'.
|
||||
* The register values are saved to or loaded from them 'buffer' address.
|
||||
*
|
||||
* Note that these functions don't update the coprocessor_owner information!
|
||||
*
|
||||
*/
|
||||
|
||||
ENTRY(coprocessor_save)
|
||||
|
||||
entry a1, 32
|
||||
s32i a0, a1, 0
|
||||
movi a0, .Lsave_cp_regs_jump_table
|
||||
addx8 a3, a3, a0
|
||||
l32i a3, a3, 0
|
||||
beqz a3, 1f
|
||||
add a0, a0, a3
|
||||
callx0 a0
|
||||
1: l32i a0, a1, 0
|
||||
retw
|
||||
|
||||
ENDPROC(coprocessor_save)
|
||||
|
||||
ENTRY(coprocessor_load)
|
||||
|
||||
entry a1, 32
|
||||
s32i a0, a1, 0
|
||||
movi a0, .Lload_cp_regs_jump_table
|
||||
addx4 a3, a3, a0
|
||||
l32i a3, a3, 0
|
||||
beqz a3, 1f
|
||||
add a0, a0, a3
|
||||
callx0 a0
|
||||
1: l32i a0, a1, 0
|
||||
retw
|
||||
|
||||
ENDPROC(coprocessor_load)
|
||||
|
||||
/*
|
||||
* coprocessor_flush(struct task_info*, index)
|
||||
* a2 a3
|
||||
* coprocessor_restore(struct task_info*, index)
|
||||
* a2 a3
|
||||
*
|
||||
* Save or load coprocessor registers for coprocessor 'index'.
|
||||
* The register values are saved to or loaded from the coprocessor area
|
||||
* inside the task_info structure.
|
||||
*
|
||||
* Note that these functions don't update the coprocessor_owner information!
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
ENTRY(coprocessor_flush)
|
||||
|
||||
entry a1, 32
|
||||
s32i a0, a1, 0
|
||||
movi a0, .Lsave_cp_regs_jump_table
|
||||
addx8 a3, a3, a0
|
||||
l32i a4, a3, 4
|
||||
l32i a3, a3, 0
|
||||
add a2, a2, a4
|
||||
beqz a3, 1f
|
||||
add a0, a0, a3
|
||||
callx0 a0
|
||||
1: l32i a0, a1, 0
|
||||
retw
|
||||
|
||||
ENDPROC(coprocessor_flush)
|
||||
|
||||
ENTRY(coprocessor_restore)
|
||||
entry a1, 32
|
||||
s32i a0, a1, 0
|
||||
movi a0, .Lload_cp_regs_jump_table
|
||||
addx4 a3, a3, a0
|
||||
l32i a4, a3, 4
|
||||
l32i a3, a3, 0
|
||||
add a2, a2, a4
|
||||
beqz a3, 1f
|
||||
add a0, a0, a3
|
||||
callx0 a0
|
||||
1: l32i a0, a1, 0
|
||||
retw
|
||||
|
||||
ENDPROC(coprocessor_restore)
|
||||
|
||||
/*
|
||||
* Entry condition:
|
||||
*
|
||||
* a0: trashed, original value saved on stack (PT_AREG0)
|
||||
* a1: a1
|
||||
* a2: new stack pointer, original in DEPC
|
||||
* a3: a3
|
||||
* depc: a2, original value saved on stack (PT_DEPC)
|
||||
* excsave_1: dispatch table
|
||||
*
|
||||
* PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC
|
||||
* < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception
|
||||
*/
|
||||
|
||||
ENTRY(fast_coprocessor_double)
|
||||
|
||||
wsr a0, excsave1
|
||||
movi a0, unrecoverable_exception
|
||||
callx0 a0
|
||||
|
||||
ENDPROC(fast_coprocessor_double)
|
||||
|
||||
ENTRY(fast_coprocessor)
|
||||
|
||||
/* Save remaining registers a1-a3 and SAR */
|
||||
|
||||
s32i a3, a2, PT_AREG3
|
||||
rsr a3, sar
|
||||
s32i a1, a2, PT_AREG1
|
||||
s32i a3, a2, PT_SAR
|
||||
mov a1, a2
|
||||
rsr a2, depc
|
||||
s32i a2, a1, PT_AREG2
|
||||
|
||||
/*
|
||||
* The hal macros require up to 4 temporary registers. We use a3..a6.
|
||||
*/
|
||||
|
||||
s32i a4, a1, PT_AREG4
|
||||
s32i a5, a1, PT_AREG5
|
||||
s32i a6, a1, PT_AREG6
|
||||
|
||||
/* Find coprocessor number. Subtract first CP EXCCAUSE from EXCCAUSE */
|
||||
|
||||
rsr a3, exccause
|
||||
addi a3, a3, -EXCCAUSE_COPROCESSOR0_DISABLED
|
||||
|
||||
/* Set corresponding CPENABLE bit -> (sar:cp-index, a3: 1<<cp-index)*/
|
||||
|
||||
ssl a3 # SAR: 32 - coprocessor_number
|
||||
movi a2, 1
|
||||
rsr a0, cpenable
|
||||
sll a2, a2
|
||||
or a0, a0, a2
|
||||
wsr a0, cpenable
|
||||
rsync
|
||||
|
||||
/* Retrieve previous owner. (a3 still holds CP number) */
|
||||
|
||||
movi a0, coprocessor_owner # list of owners
|
||||
addx4 a0, a3, a0 # entry for CP
|
||||
l32i a4, a0, 0
|
||||
|
||||
beqz a4, 1f # skip 'save' if no previous owner
|
||||
|
||||
/* Disable coprocessor for previous owner. (a2 = 1 << CP number) */
|
||||
|
||||
l32i a5, a4, THREAD_CPENABLE
|
||||
xor a5, a5, a2 # (1 << cp-id) still in a2
|
||||
s32i a5, a4, THREAD_CPENABLE
|
||||
|
||||
/*
|
||||
* Get context save area and 'call' save routine.
|
||||
* (a4 still holds previous owner (thread_info), a3 CP number)
|
||||
*/
|
||||
|
||||
movi a5, .Lsave_cp_regs_jump_table
|
||||
movi a0, 2f # a0: 'return' address
|
||||
addx8 a3, a3, a5 # a3: coprocessor number
|
||||
l32i a2, a3, 4 # a2: xtregs offset
|
||||
l32i a3, a3, 0 # a3: jump offset
|
||||
add a2, a2, a4
|
||||
add a4, a3, a5 # a4: address of save routine
|
||||
jx a4
|
||||
|
||||
/* Note that only a0 and a1 were preserved. */
|
||||
|
||||
2: rsr a3, exccause
|
||||
addi a3, a3, -EXCCAUSE_COPROCESSOR0_DISABLED
|
||||
movi a0, coprocessor_owner
|
||||
addx4 a0, a3, a0
|
||||
|
||||
/* Set new 'owner' (a0 points to the CP owner, a3 contains the CP nr) */
|
||||
|
||||
1: GET_THREAD_INFO (a4, a1)
|
||||
s32i a4, a0, 0
|
||||
|
||||
/* Get context save area and 'call' load routine. */
|
||||
|
||||
movi a5, .Lload_cp_regs_jump_table
|
||||
movi a0, 1f
|
||||
addx8 a3, a3, a5
|
||||
l32i a2, a3, 4 # a2: xtregs offset
|
||||
l32i a3, a3, 0 # a3: jump offset
|
||||
add a2, a2, a4
|
||||
add a4, a3, a5
|
||||
jx a4
|
||||
|
||||
/* Restore all registers and return from exception handler. */
|
||||
|
||||
1: l32i a6, a1, PT_AREG6
|
||||
l32i a5, a1, PT_AREG5
|
||||
l32i a4, a1, PT_AREG4
|
||||
|
||||
l32i a0, a1, PT_SAR
|
||||
l32i a3, a1, PT_AREG3
|
||||
l32i a2, a1, PT_AREG2
|
||||
wsr a0, sar
|
||||
l32i a0, a1, PT_AREG0
|
||||
l32i a1, a1, PT_AREG1
|
||||
|
||||
rfe
|
||||
|
||||
ENDPROC(fast_coprocessor)
|
||||
|
||||
.data
|
||||
|
||||
ENTRY(coprocessor_owner)
|
||||
|
||||
.fill XCHAL_CP_MAX, 4, 0
|
||||
|
||||
END(coprocessor_owner)
|
||||
|
||||
#endif /* XTENSA_HAVE_COPROCESSORS */
|
1938
arch/xtensa/kernel/entry.S
Normal file
1938
arch/xtensa/kernel/entry.S
Normal file
File diff suppressed because it is too large
Load diff
375
arch/xtensa/kernel/head.S
Normal file
375
arch/xtensa/kernel/head.S
Normal file
|
@ -0,0 +1,375 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/head.S
|
||||
*
|
||||
* Xtensa Processor startup code.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2008 Tensilica Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Marc Gauthier <marc@tensilica.com, marc@alumni.uwaterloo.ca>
|
||||
* Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
* Kevin Chea
|
||||
*/
|
||||
|
||||
#include <asm/processor.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/cacheasm.h>
|
||||
#include <asm/initialize_mmu.h>
|
||||
#include <asm/mxregs.h>
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/linkage.h>
|
||||
|
||||
/*
|
||||
* This module contains the entry code for kernel images. It performs the
|
||||
* minimal setup needed to call the generic C routines.
|
||||
*
|
||||
* Prerequisites:
|
||||
*
|
||||
* - The kernel image has been loaded to the actual address where it was
|
||||
* compiled to.
|
||||
* - a2 contains either 0 or a pointer to a list of boot parameters.
|
||||
* (see setup.c for more details)
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* _start
|
||||
*
|
||||
* The bootloader passes a pointer to a list of boot parameters in a2.
|
||||
*/
|
||||
|
||||
/* The first bytes of the kernel image must be an instruction, so we
|
||||
* manually allocate and define the literal constant we need for a jx
|
||||
* instruction.
|
||||
*/
|
||||
|
||||
__HEAD
|
||||
.begin no-absolute-literals
|
||||
|
||||
ENTRY(_start)
|
||||
|
||||
/* Preserve the pointer to the boot parameter list in EXCSAVE_1 */
|
||||
wsr a2, excsave1
|
||||
_j _SetupOCD
|
||||
|
||||
.align 4
|
||||
.literal_position
|
||||
.Lstartup:
|
||||
.word _startup
|
||||
|
||||
.align 4
|
||||
_SetupOCD:
|
||||
/*
|
||||
* Initialize WB, WS, and clear PS.EXCM (to allow loop instructions).
|
||||
* Set Interrupt Level just below XCHAL_DEBUGLEVEL to allow
|
||||
* xt-gdb to single step via DEBUG exceptions received directly
|
||||
* by ocd.
|
||||
*/
|
||||
movi a1, 1
|
||||
movi a0, 0
|
||||
wsr a1, windowstart
|
||||
wsr a0, windowbase
|
||||
rsync
|
||||
|
||||
movi a1, LOCKLEVEL
|
||||
wsr a1, ps
|
||||
rsync
|
||||
|
||||
.global _SetupMMU
|
||||
_SetupMMU:
|
||||
Offset = _SetupMMU - _start
|
||||
|
||||
#ifdef CONFIG_INITIALIZE_XTENSA_MMU_INSIDE_VMLINUX
|
||||
initialize_mmu
|
||||
#if defined(CONFIG_MMU) && XCHAL_HAVE_PTP_MMU && XCHAL_HAVE_SPANNING_WAY
|
||||
rsr a2, excsave1
|
||||
movi a3, 0x08000000
|
||||
bgeu a2, a3, 1f
|
||||
movi a3, 0xd0000000
|
||||
add a2, a2, a3
|
||||
wsr a2, excsave1
|
||||
1:
|
||||
#endif
|
||||
#endif
|
||||
.end no-absolute-literals
|
||||
|
||||
l32r a0, .Lstartup
|
||||
jx a0
|
||||
|
||||
ENDPROC(_start)
|
||||
|
||||
__REF
|
||||
.literal_position
|
||||
|
||||
ENTRY(_startup)
|
||||
|
||||
/* Set a0 to 0 for the remaining initialization. */
|
||||
|
||||
movi a0, 0
|
||||
|
||||
/* Clear debugging registers. */
|
||||
|
||||
#if XCHAL_HAVE_DEBUG
|
||||
#if XCHAL_NUM_IBREAK > 0
|
||||
wsr a0, ibreakenable
|
||||
#endif
|
||||
wsr a0, icount
|
||||
movi a1, 15
|
||||
wsr a0, icountlevel
|
||||
|
||||
.set _index, 0
|
||||
.rept XCHAL_NUM_DBREAK - 1
|
||||
wsr a0, SREG_DBREAKC + _index
|
||||
.set _index, _index + 1
|
||||
.endr
|
||||
#endif
|
||||
|
||||
/* Clear CCOUNT (not really necessary, but nice) */
|
||||
|
||||
wsr a0, ccount # not really necessary, but nice
|
||||
|
||||
/* Disable zero-loops. */
|
||||
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
wsr a0, lcount
|
||||
#endif
|
||||
|
||||
/* Disable all timers. */
|
||||
|
||||
.set _index, 0
|
||||
.rept XCHAL_NUM_TIMERS
|
||||
wsr a0, SREG_CCOMPARE + _index
|
||||
.set _index, _index + 1
|
||||
.endr
|
||||
|
||||
/* Interrupt initialization. */
|
||||
|
||||
movi a2, XCHAL_INTTYPE_MASK_SOFTWARE | XCHAL_INTTYPE_MASK_EXTERN_EDGE
|
||||
wsr a0, intenable
|
||||
wsr a2, intclear
|
||||
|
||||
/* Disable coprocessors. */
|
||||
|
||||
#if XCHAL_HAVE_CP
|
||||
wsr a0, cpenable
|
||||
#endif
|
||||
|
||||
/* Initialize the caches.
|
||||
* a2, a3 are just working registers (clobbered).
|
||||
*/
|
||||
|
||||
#if XCHAL_DCACHE_LINE_LOCKABLE
|
||||
___unlock_dcache_all a2 a3
|
||||
#endif
|
||||
|
||||
#if XCHAL_ICACHE_LINE_LOCKABLE
|
||||
___unlock_icache_all a2 a3
|
||||
#endif
|
||||
|
||||
___invalidate_dcache_all a2 a3
|
||||
___invalidate_icache_all a2 a3
|
||||
|
||||
isync
|
||||
|
||||
#ifdef CONFIG_HAVE_SMP
|
||||
movi a2, CCON # MX External Register to Configure Cache
|
||||
movi a3, 1
|
||||
wer a3, a2
|
||||
#endif
|
||||
|
||||
/* Setup stack and enable window exceptions (keep irqs disabled) */
|
||||
|
||||
movi a1, start_info
|
||||
l32i a1, a1, 0
|
||||
|
||||
movi a2, (1 << PS_WOE_BIT) | LOCKLEVEL
|
||||
# WOE=1, INTLEVEL=LOCKLEVEL, UM=0
|
||||
wsr a2, ps # (enable reg-windows; progmode stack)
|
||||
rsync
|
||||
|
||||
/* Set up EXCSAVE[DEBUGLEVEL] to point to the Debug Exception Handler.*/
|
||||
|
||||
movi a2, debug_exception
|
||||
wsr a2, SREG_EXCSAVE + XCHAL_DEBUGLEVEL
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
/*
|
||||
* Notice that we assume with SMP that cores have PRID
|
||||
* supported by the cores.
|
||||
*/
|
||||
rsr a2, prid
|
||||
bnez a2, .Lboot_secondary
|
||||
|
||||
#endif /* CONFIG_SMP */
|
||||
|
||||
/* Unpack data sections
|
||||
*
|
||||
* The linker script used to build the Linux kernel image
|
||||
* creates a table located at __boot_reloc_table_start
|
||||
* that contans the information what data needs to be unpacked.
|
||||
*
|
||||
* Uses a2-a7.
|
||||
*/
|
||||
|
||||
movi a2, __boot_reloc_table_start
|
||||
movi a3, __boot_reloc_table_end
|
||||
|
||||
1: beq a2, a3, 3f # no more entries?
|
||||
l32i a4, a2, 0 # start destination (in RAM)
|
||||
l32i a5, a2, 4 # end desination (in RAM)
|
||||
l32i a6, a2, 8 # start source (in ROM)
|
||||
addi a2, a2, 12 # next entry
|
||||
beq a4, a5, 1b # skip, empty entry
|
||||
beq a4, a6, 1b # skip, source and dest. are the same
|
||||
|
||||
2: l32i a7, a6, 0 # load word
|
||||
addi a6, a6, 4
|
||||
s32i a7, a4, 0 # store word
|
||||
addi a4, a4, 4
|
||||
bltu a4, a5, 2b
|
||||
j 1b
|
||||
|
||||
3:
|
||||
/* All code and initialized data segments have been copied.
|
||||
* Now clear the BSS segment.
|
||||
*/
|
||||
|
||||
movi a2, __bss_start # start of BSS
|
||||
movi a3, __bss_stop # end of BSS
|
||||
|
||||
__loopt a2, a3, a4, 2
|
||||
s32i a0, a2, 0
|
||||
__endla a2, a4, 4
|
||||
|
||||
#if XCHAL_DCACHE_IS_WRITEBACK
|
||||
|
||||
/* After unpacking, flush the writeback cache to memory so the
|
||||
* instructions/data are available.
|
||||
*/
|
||||
|
||||
___flush_dcache_all a2 a3
|
||||
#endif
|
||||
memw
|
||||
isync
|
||||
___invalidate_icache_all a2 a3
|
||||
isync
|
||||
|
||||
movi a6, 0
|
||||
xsr a6, excsave1
|
||||
|
||||
/* init_arch kick-starts the linux kernel */
|
||||
|
||||
movi a4, init_arch
|
||||
callx4 a4
|
||||
|
||||
movi a4, start_kernel
|
||||
callx4 a4
|
||||
|
||||
should_never_return:
|
||||
j should_never_return
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
.Lboot_secondary:
|
||||
|
||||
movi a2, cpu_start_ccount
|
||||
1:
|
||||
l32i a3, a2, 0
|
||||
beqi a3, 0, 1b
|
||||
movi a3, 0
|
||||
s32i a3, a2, 0
|
||||
memw
|
||||
1:
|
||||
l32i a3, a2, 0
|
||||
beqi a3, 0, 1b
|
||||
wsr a3, ccount
|
||||
movi a3, 0
|
||||
s32i a3, a2, 0
|
||||
memw
|
||||
|
||||
movi a6, 0
|
||||
wsr a6, excsave1
|
||||
|
||||
movi a4, secondary_start_kernel
|
||||
callx4 a4
|
||||
j should_never_return
|
||||
|
||||
#endif /* CONFIG_SMP */
|
||||
|
||||
ENDPROC(_startup)
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
|
||||
ENTRY(cpu_restart)
|
||||
|
||||
#if XCHAL_DCACHE_IS_WRITEBACK
|
||||
___flush_invalidate_dcache_all a2 a3
|
||||
#else
|
||||
___invalidate_dcache_all a2 a3
|
||||
#endif
|
||||
memw
|
||||
movi a2, CCON # MX External Register to Configure Cache
|
||||
movi a3, 0
|
||||
wer a3, a2
|
||||
extw
|
||||
|
||||
rsr a0, prid
|
||||
neg a2, a0
|
||||
movi a3, cpu_start_id
|
||||
s32i a2, a3, 0
|
||||
#if XCHAL_DCACHE_IS_WRITEBACK
|
||||
dhwbi a3, 0
|
||||
#endif
|
||||
1:
|
||||
l32i a2, a3, 0
|
||||
dhi a3, 0
|
||||
bne a2, a0, 1b
|
||||
|
||||
/*
|
||||
* Initialize WB, WS, and clear PS.EXCM (to allow loop instructions).
|
||||
* Set Interrupt Level just below XCHAL_DEBUGLEVEL to allow
|
||||
* xt-gdb to single step via DEBUG exceptions received directly
|
||||
* by ocd.
|
||||
*/
|
||||
movi a1, 1
|
||||
movi a0, 0
|
||||
wsr a1, windowstart
|
||||
wsr a0, windowbase
|
||||
rsync
|
||||
|
||||
movi a1, LOCKLEVEL
|
||||
wsr a1, ps
|
||||
rsync
|
||||
|
||||
j _startup
|
||||
|
||||
ENDPROC(cpu_restart)
|
||||
|
||||
#endif /* CONFIG_HOTPLUG_CPU */
|
||||
|
||||
/*
|
||||
* DATA section
|
||||
*/
|
||||
|
||||
.section ".data.init.refok"
|
||||
.align 4
|
||||
ENTRY(start_info)
|
||||
.long init_thread_union + KERNEL_STACK_SIZE
|
||||
|
||||
/*
|
||||
* BSS section
|
||||
*/
|
||||
|
||||
__PAGE_ALIGNED_BSS
|
||||
#ifdef CONFIG_MMU
|
||||
ENTRY(swapper_pg_dir)
|
||||
.fill PAGE_SIZE, 1, 0
|
||||
END(swapper_pg_dir)
|
||||
#endif
|
||||
ENTRY(empty_zero_page)
|
||||
.fill PAGE_SIZE, 1, 0
|
||||
END(empty_zero_page)
|
188
arch/xtensa/kernel/irq.c
Normal file
188
arch/xtensa/kernel/irq.c
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* linux/arch/xtensa/kernel/irq.c
|
||||
*
|
||||
* Xtensa built-in interrupt controller and some generic functions copied
|
||||
* from i386.
|
||||
*
|
||||
* Copyright (C) 2002 - 2013 Tensilica, Inc.
|
||||
* Copyright (C) 1992, 1998 Linus Torvalds, Ingo Molnar
|
||||
*
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Kevin Chea
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kernel_stat.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/xtensa-mx.h>
|
||||
#include <linux/irqchip/xtensa-pic.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <asm/mxregs.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/platform.h>
|
||||
|
||||
atomic_t irq_err_count;
|
||||
|
||||
asmlinkage void do_IRQ(int hwirq, struct pt_regs *regs)
|
||||
{
|
||||
int irq = irq_find_mapping(NULL, hwirq);
|
||||
|
||||
if (hwirq >= NR_IRQS) {
|
||||
printk(KERN_EMERG "%s: cannot handle IRQ %d\n",
|
||||
__func__, hwirq);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_STACKOVERFLOW
|
||||
/* Debugging check for stack overflow: is there less than 1KB free? */
|
||||
{
|
||||
unsigned long sp;
|
||||
|
||||
__asm__ __volatile__ ("mov %0, a1\n" : "=a" (sp));
|
||||
sp &= THREAD_SIZE - 1;
|
||||
|
||||
if (unlikely(sp < (sizeof(thread_info) + 1024)))
|
||||
printk("Stack overflow in do_IRQ: %ld\n",
|
||||
sp - sizeof(struct thread_info));
|
||||
}
|
||||
#endif
|
||||
generic_handle_irq(irq);
|
||||
}
|
||||
|
||||
int arch_show_interrupts(struct seq_file *p, int prec)
|
||||
{
|
||||
#ifdef CONFIG_SMP
|
||||
show_ipi_list(p, prec);
|
||||
#endif
|
||||
seq_printf(p, "%*s: ", prec, "ERR");
|
||||
seq_printf(p, "%10u\n", atomic_read(&irq_err_count));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xtensa_irq_domain_xlate(const u32 *intspec, unsigned int intsize,
|
||||
unsigned long int_irq, unsigned long ext_irq,
|
||||
unsigned long *out_hwirq, unsigned int *out_type)
|
||||
{
|
||||
if (WARN_ON(intsize < 1 || intsize > 2))
|
||||
return -EINVAL;
|
||||
if (intsize == 2 && intspec[1] == 1) {
|
||||
int_irq = xtensa_map_ext_irq(ext_irq);
|
||||
if (int_irq < XCHAL_NUM_INTERRUPTS)
|
||||
*out_hwirq = int_irq;
|
||||
else
|
||||
return -EINVAL;
|
||||
} else {
|
||||
*out_hwirq = int_irq;
|
||||
}
|
||||
*out_type = IRQ_TYPE_NONE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xtensa_irq_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
struct irq_chip *irq_chip = d->host_data;
|
||||
u32 mask = 1 << hw;
|
||||
|
||||
if (mask & XCHAL_INTTYPE_MASK_SOFTWARE) {
|
||||
irq_set_chip_and_handler_name(irq, irq_chip,
|
||||
handle_simple_irq, "level");
|
||||
irq_set_status_flags(irq, IRQ_LEVEL);
|
||||
} else if (mask & XCHAL_INTTYPE_MASK_EXTERN_EDGE) {
|
||||
irq_set_chip_and_handler_name(irq, irq_chip,
|
||||
handle_edge_irq, "edge");
|
||||
irq_clear_status_flags(irq, IRQ_LEVEL);
|
||||
} else if (mask & XCHAL_INTTYPE_MASK_EXTERN_LEVEL) {
|
||||
irq_set_chip_and_handler_name(irq, irq_chip,
|
||||
handle_level_irq, "level");
|
||||
irq_set_status_flags(irq, IRQ_LEVEL);
|
||||
} else if (mask & XCHAL_INTTYPE_MASK_TIMER) {
|
||||
irq_set_chip_and_handler_name(irq, irq_chip,
|
||||
handle_percpu_irq, "timer");
|
||||
irq_clear_status_flags(irq, IRQ_LEVEL);
|
||||
} else {/* XCHAL_INTTYPE_MASK_WRITE_ERROR */
|
||||
/* XCHAL_INTTYPE_MASK_NMI */
|
||||
irq_set_chip_and_handler_name(irq, irq_chip,
|
||||
handle_level_irq, "level");
|
||||
irq_set_status_flags(irq, IRQ_LEVEL);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned xtensa_map_ext_irq(unsigned ext_irq)
|
||||
{
|
||||
unsigned mask = XCHAL_INTTYPE_MASK_EXTERN_EDGE |
|
||||
XCHAL_INTTYPE_MASK_EXTERN_LEVEL;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; mask; ++i, mask >>= 1) {
|
||||
if ((mask & 1) && ext_irq-- == 0)
|
||||
return i;
|
||||
}
|
||||
return XCHAL_NUM_INTERRUPTS;
|
||||
}
|
||||
|
||||
unsigned xtensa_get_ext_irq_no(unsigned irq)
|
||||
{
|
||||
unsigned mask = (XCHAL_INTTYPE_MASK_EXTERN_EDGE |
|
||||
XCHAL_INTTYPE_MASK_EXTERN_LEVEL) &
|
||||
((1u << irq) - 1);
|
||||
return hweight32(mask);
|
||||
}
|
||||
|
||||
void __init init_IRQ(void)
|
||||
{
|
||||
#ifdef CONFIG_OF
|
||||
irqchip_init();
|
||||
#else
|
||||
#ifdef CONFIG_HAVE_SMP
|
||||
xtensa_mx_init_legacy(NULL);
|
||||
#else
|
||||
xtensa_pic_init_legacy(NULL);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
ipi_init();
|
||||
#endif
|
||||
variant_init_irq();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
/*
|
||||
* The CPU has been marked offline. Migrate IRQs off this CPU. If
|
||||
* the affinity settings do not allow other CPUs, force them onto any
|
||||
* available CPU.
|
||||
*/
|
||||
void migrate_irqs(void)
|
||||
{
|
||||
unsigned int i, cpu = smp_processor_id();
|
||||
|
||||
for_each_active_irq(i) {
|
||||
struct irq_data *data = irq_get_irq_data(i);
|
||||
unsigned int newcpu;
|
||||
|
||||
if (irqd_is_per_cpu(data))
|
||||
continue;
|
||||
|
||||
if (!cpumask_test_cpu(cpu, data->affinity))
|
||||
continue;
|
||||
|
||||
newcpu = cpumask_any_and(data->affinity, cpu_online_mask);
|
||||
|
||||
if (newcpu >= nr_cpu_ids) {
|
||||
pr_info_ratelimited("IRQ%u no longer affine to CPU%u\n",
|
||||
i, cpu);
|
||||
|
||||
cpumask_setall(data->affinity);
|
||||
}
|
||||
irq_set_affinity(i, data->affinity);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_HOTPLUG_CPU */
|
50
arch/xtensa/kernel/mcount.S
Normal file
50
arch/xtensa/kernel/mcount.S
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/mcount.S
|
||||
*
|
||||
* Xtensa specific mcount support
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2013 Tensilica Inc.
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/ftrace.h>
|
||||
|
||||
/*
|
||||
* Entry condition:
|
||||
*
|
||||
* a2: a0 of the caller
|
||||
*/
|
||||
|
||||
ENTRY(_mcount)
|
||||
|
||||
entry a1, 16
|
||||
|
||||
movi a4, ftrace_trace_function
|
||||
l32i a4, a4, 0
|
||||
movi a3, ftrace_stub
|
||||
bne a3, a4, 1f
|
||||
retw
|
||||
|
||||
1: xor a7, a2, a1
|
||||
movi a3, 0x3fffffff
|
||||
and a7, a7, a3
|
||||
xor a7, a7, a1
|
||||
|
||||
xor a6, a0, a1
|
||||
and a6, a6, a3
|
||||
xor a6, a6, a1
|
||||
addi a6, a6, -MCOUNT_INSN_SIZE
|
||||
callx4 a4
|
||||
|
||||
retw
|
||||
|
||||
ENDPROC(_mcount)
|
||||
|
||||
ENTRY(ftrace_stub)
|
||||
entry a1, 16
|
||||
retw
|
||||
ENDPROC(ftrace_stub)
|
192
arch/xtensa/kernel/module.c
Normal file
192
arch/xtensa/kernel/module.c
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/module.c
|
||||
*
|
||||
* Module support.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2006 Tensilica Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleloader.h>
|
||||
#include <linux/elf.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/cache.h>
|
||||
|
||||
#undef DEBUG_RELOCATE
|
||||
|
||||
static int
|
||||
decode_calln_opcode (unsigned char *location)
|
||||
{
|
||||
#ifdef __XTENSA_EB__
|
||||
return (location[0] & 0xf0) == 0x50;
|
||||
#endif
|
||||
#ifdef __XTENSA_EL__
|
||||
return (location[0] & 0xf) == 0x5;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
decode_l32r_opcode (unsigned char *location)
|
||||
{
|
||||
#ifdef __XTENSA_EB__
|
||||
return (location[0] & 0xf0) == 0x10;
|
||||
#endif
|
||||
#ifdef __XTENSA_EL__
|
||||
return (location[0] & 0xf) == 0x1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int apply_relocate_add(Elf32_Shdr *sechdrs,
|
||||
const char *strtab,
|
||||
unsigned int symindex,
|
||||
unsigned int relsec,
|
||||
struct module *mod)
|
||||
{
|
||||
unsigned int i;
|
||||
Elf32_Rela *rela = (void *)sechdrs[relsec].sh_addr;
|
||||
Elf32_Sym *sym;
|
||||
unsigned char *location;
|
||||
uint32_t value;
|
||||
|
||||
#ifdef DEBUG_RELOCATE
|
||||
printk("Applying relocate section %u to %u\n", relsec,
|
||||
sechdrs[relsec].sh_info);
|
||||
#endif
|
||||
for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rela); i++) {
|
||||
location = (char *)sechdrs[sechdrs[relsec].sh_info].sh_addr
|
||||
+ rela[i].r_offset;
|
||||
sym = (Elf32_Sym *)sechdrs[symindex].sh_addr
|
||||
+ ELF32_R_SYM(rela[i].r_info);
|
||||
value = sym->st_value + rela[i].r_addend;
|
||||
|
||||
switch (ELF32_R_TYPE(rela[i].r_info)) {
|
||||
case R_XTENSA_NONE:
|
||||
case R_XTENSA_DIFF8:
|
||||
case R_XTENSA_DIFF16:
|
||||
case R_XTENSA_DIFF32:
|
||||
case R_XTENSA_ASM_EXPAND:
|
||||
break;
|
||||
|
||||
case R_XTENSA_32:
|
||||
case R_XTENSA_PLT:
|
||||
*(uint32_t *)location += value;
|
||||
break;
|
||||
|
||||
case R_XTENSA_SLOT0_OP:
|
||||
if (decode_calln_opcode(location)) {
|
||||
value -= ((unsigned long)location & -4) + 4;
|
||||
if ((value & 3) != 0 ||
|
||||
((value + (1 << 19)) >> 20) != 0) {
|
||||
printk("%s: relocation out of range, "
|
||||
"section %d reloc %d "
|
||||
"sym '%s'\n",
|
||||
mod->name, relsec, i,
|
||||
strtab + sym->st_name);
|
||||
return -ENOEXEC;
|
||||
}
|
||||
value = (signed int)value >> 2;
|
||||
#ifdef __XTENSA_EB__
|
||||
location[0] = ((location[0] & ~0x3) |
|
||||
((value >> 16) & 0x3));
|
||||
location[1] = (value >> 8) & 0xff;
|
||||
location[2] = value & 0xff;
|
||||
#endif
|
||||
#ifdef __XTENSA_EL__
|
||||
location[0] = ((location[0] & ~0xc0) |
|
||||
((value << 6) & 0xc0));
|
||||
location[1] = (value >> 2) & 0xff;
|
||||
location[2] = (value >> 10) & 0xff;
|
||||
#endif
|
||||
} else if (decode_l32r_opcode(location)) {
|
||||
value -= (((unsigned long)location + 3) & -4);
|
||||
if ((value & 3) != 0 ||
|
||||
(signed int)value >> 18 != -1) {
|
||||
printk("%s: relocation out of range, "
|
||||
"section %d reloc %d "
|
||||
"sym '%s'\n",
|
||||
mod->name, relsec, i,
|
||||
strtab + sym->st_name);
|
||||
return -ENOEXEC;
|
||||
}
|
||||
value = (signed int)value >> 2;
|
||||
|
||||
#ifdef __XTENSA_EB__
|
||||
location[1] = (value >> 8) & 0xff;
|
||||
location[2] = value & 0xff;
|
||||
#endif
|
||||
#ifdef __XTENSA_EL__
|
||||
location[1] = value & 0xff;
|
||||
location[2] = (value >> 8) & 0xff;
|
||||
#endif
|
||||
}
|
||||
/* FIXME: Ignore any other opcodes. The Xtensa
|
||||
assembler currently assumes that the linker will
|
||||
always do relaxation and so all PC-relative
|
||||
operands need relocations. (The assembler also
|
||||
writes out the tentative PC-relative values,
|
||||
assuming no link-time relaxation, so it is usually
|
||||
safe to ignore the relocations.) If the
|
||||
assembler's "--no-link-relax" flag can be made to
|
||||
work, and if all kernel modules can be assembled
|
||||
with that flag, then unexpected relocations could
|
||||
be detected here. */
|
||||
break;
|
||||
|
||||
case R_XTENSA_SLOT1_OP:
|
||||
case R_XTENSA_SLOT2_OP:
|
||||
case R_XTENSA_SLOT3_OP:
|
||||
case R_XTENSA_SLOT4_OP:
|
||||
case R_XTENSA_SLOT5_OP:
|
||||
case R_XTENSA_SLOT6_OP:
|
||||
case R_XTENSA_SLOT7_OP:
|
||||
case R_XTENSA_SLOT8_OP:
|
||||
case R_XTENSA_SLOT9_OP:
|
||||
case R_XTENSA_SLOT10_OP:
|
||||
case R_XTENSA_SLOT11_OP:
|
||||
case R_XTENSA_SLOT12_OP:
|
||||
case R_XTENSA_SLOT13_OP:
|
||||
case R_XTENSA_SLOT14_OP:
|
||||
printk("%s: unexpected FLIX relocation: %u\n",
|
||||
mod->name,
|
||||
ELF32_R_TYPE(rela[i].r_info));
|
||||
return -ENOEXEC;
|
||||
|
||||
case R_XTENSA_SLOT0_ALT:
|
||||
case R_XTENSA_SLOT1_ALT:
|
||||
case R_XTENSA_SLOT2_ALT:
|
||||
case R_XTENSA_SLOT3_ALT:
|
||||
case R_XTENSA_SLOT4_ALT:
|
||||
case R_XTENSA_SLOT5_ALT:
|
||||
case R_XTENSA_SLOT6_ALT:
|
||||
case R_XTENSA_SLOT7_ALT:
|
||||
case R_XTENSA_SLOT8_ALT:
|
||||
case R_XTENSA_SLOT9_ALT:
|
||||
case R_XTENSA_SLOT10_ALT:
|
||||
case R_XTENSA_SLOT11_ALT:
|
||||
case R_XTENSA_SLOT12_ALT:
|
||||
case R_XTENSA_SLOT13_ALT:
|
||||
case R_XTENSA_SLOT14_ALT:
|
||||
printk("%s: unexpected ALT relocation: %u\n",
|
||||
mod->name,
|
||||
ELF32_R_TYPE(rela[i].r_info));
|
||||
return -ENOEXEC;
|
||||
|
||||
default:
|
||||
printk("%s: unexpected relocation: %u\n",
|
||||
mod->name,
|
||||
ELF32_R_TYPE(rela[i].r_info));
|
||||
return -ENOEXEC;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
85
arch/xtensa/kernel/mxhead.S
Normal file
85
arch/xtensa/kernel/mxhead.S
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Xtensa Secondary Processors startup code.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2013 Tensilica Inc.
|
||||
*
|
||||
* Joe Taylor <joe@tensilica.com>
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Marc Gauthier <marc@tensilica.com, marc@alumni.uwaterloo.ca>
|
||||
* Pete Delaney <piet@tensilica.com>
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
|
||||
#include <asm/cacheasm.h>
|
||||
#include <asm/initialize_mmu.h>
|
||||
#include <asm/mxregs.h>
|
||||
#include <asm/regs.h>
|
||||
|
||||
|
||||
.section .SecondaryResetVector.text, "ax"
|
||||
|
||||
|
||||
ENTRY(_SecondaryResetVector)
|
||||
_j _SetupOCD
|
||||
|
||||
.begin no-absolute-literals
|
||||
.literal_position
|
||||
|
||||
_SetupOCD:
|
||||
/*
|
||||
* Initialize WB, WS, and clear PS.EXCM (to allow loop instructions).
|
||||
* Set Interrupt Level just below XCHAL_DEBUGLEVEL to allow
|
||||
* xt-gdb to single step via DEBUG exceptions received directly
|
||||
* by ocd.
|
||||
*/
|
||||
movi a1, 1
|
||||
movi a0, 0
|
||||
wsr a1, windowstart
|
||||
wsr a0, windowbase
|
||||
rsync
|
||||
|
||||
movi a1, LOCKLEVEL
|
||||
wsr a1, ps
|
||||
rsync
|
||||
|
||||
_SetupMMU:
|
||||
Offset = _SetupMMU - _SecondaryResetVector
|
||||
|
||||
#ifdef CONFIG_INITIALIZE_XTENSA_MMU_INSIDE_VMLINUX
|
||||
initialize_mmu
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Start Secondary Processors with NULL pointer to boot params.
|
||||
*/
|
||||
movi a2, 0 # a2 == NULL
|
||||
movi a3, _startup
|
||||
jx a3
|
||||
|
||||
.end no-absolute-literals
|
||||
|
||||
|
||||
.section .SecondaryResetVector.remapped_text, "ax"
|
||||
.global _RemappedSecondaryResetVector
|
||||
|
||||
.org 0 # Need to do org before literals
|
||||
|
||||
_RemappedSecondaryResetVector:
|
||||
.begin no-absolute-literals
|
||||
.literal_position
|
||||
|
||||
_j _RemappedSetupMMU
|
||||
. = _RemappedSecondaryResetVector + Offset
|
||||
|
||||
_RemappedSetupMMU:
|
||||
|
||||
#ifdef CONFIG_INITIALIZE_XTENSA_MMU_INSIDE_VMLINUX
|
||||
initialize_mmu
|
||||
#endif
|
||||
|
||||
.end no-absolute-literals
|
98
arch/xtensa/kernel/pci-dma.c
Normal file
98
arch/xtensa/kernel/pci-dma.c
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/pci-dma.c
|
||||
*
|
||||
* DMA coherent memory allocation.
|
||||
*
|
||||
* 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; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Copyright (C) 2002 - 2005 Tensilica Inc.
|
||||
*
|
||||
* Based on version for i386.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/module.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/cacheflush.h>
|
||||
|
||||
/*
|
||||
* Note: We assume that the full memory space is always mapped to 'kseg'
|
||||
* Otherwise we have to use page attributes (not implemented).
|
||||
*/
|
||||
|
||||
void *
|
||||
dma_alloc_coherent(struct device *dev,size_t size,dma_addr_t *handle,gfp_t flag)
|
||||
{
|
||||
unsigned long ret;
|
||||
unsigned long uncached = 0;
|
||||
|
||||
/* ignore region speicifiers */
|
||||
|
||||
flag &= ~(__GFP_DMA | __GFP_HIGHMEM);
|
||||
|
||||
if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff))
|
||||
flag |= GFP_DMA;
|
||||
ret = (unsigned long)__get_free_pages(flag, get_order(size));
|
||||
|
||||
if (ret == 0)
|
||||
return NULL;
|
||||
|
||||
/* We currently don't support coherent memory outside KSEG */
|
||||
|
||||
BUG_ON(ret < XCHAL_KSEG_CACHED_VADDR ||
|
||||
ret > XCHAL_KSEG_CACHED_VADDR + XCHAL_KSEG_SIZE - 1);
|
||||
|
||||
|
||||
if (ret != 0) {
|
||||
memset((void*) ret, 0, size);
|
||||
uncached = ret+XCHAL_KSEG_BYPASS_VADDR-XCHAL_KSEG_CACHED_VADDR;
|
||||
*handle = virt_to_bus((void*)ret);
|
||||
__flush_invalidate_dcache_range(ret, size);
|
||||
}
|
||||
|
||||
return (void*)uncached;
|
||||
}
|
||||
EXPORT_SYMBOL(dma_alloc_coherent);
|
||||
|
||||
void dma_free_coherent(struct device *hwdev, size_t size,
|
||||
void *vaddr, dma_addr_t dma_handle)
|
||||
{
|
||||
unsigned long addr = (unsigned long)vaddr +
|
||||
XCHAL_KSEG_CACHED_VADDR - XCHAL_KSEG_BYPASS_VADDR;
|
||||
|
||||
BUG_ON(addr < XCHAL_KSEG_CACHED_VADDR ||
|
||||
addr > XCHAL_KSEG_CACHED_VADDR + XCHAL_KSEG_SIZE - 1);
|
||||
|
||||
free_pages(addr, get_order(size));
|
||||
}
|
||||
EXPORT_SYMBOL(dma_free_coherent);
|
||||
|
||||
|
||||
void consistent_sync(void *vaddr, size_t size, int direction)
|
||||
{
|
||||
switch (direction) {
|
||||
case PCI_DMA_NONE:
|
||||
BUG();
|
||||
case PCI_DMA_FROMDEVICE: /* invalidate only */
|
||||
__invalidate_dcache_range((unsigned long)vaddr,
|
||||
(unsigned long)size);
|
||||
break;
|
||||
|
||||
case PCI_DMA_TODEVICE: /* writeback only */
|
||||
case PCI_DMA_BIDIRECTIONAL: /* writeback and invalidate */
|
||||
__flush_invalidate_dcache_range((unsigned long)vaddr,
|
||||
(unsigned long)size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(consistent_sync);
|
370
arch/xtensa/kernel/pci.c
Normal file
370
arch/xtensa/kernel/pci.c
Normal file
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/pci.c
|
||||
*
|
||||
* PCI bios-type initialisation for PCI machines
|
||||
*
|
||||
* 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; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Copyright (C) 2001-2005 Tensilica Inc.
|
||||
*
|
||||
* Based largely on work from Cort (ppc/kernel/pci.c)
|
||||
* IO functions copied from sparc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/bootmem.h>
|
||||
|
||||
#include <asm/pci-bridge.h>
|
||||
#include <asm/platform.h>
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DBG(x...) printk(x)
|
||||
#else
|
||||
#define DBG(x...)
|
||||
#endif
|
||||
|
||||
/* PCI Controller */
|
||||
|
||||
|
||||
/*
|
||||
* pcibios_alloc_controller
|
||||
* pcibios_enable_device
|
||||
* pcibios_fixups
|
||||
* pcibios_align_resource
|
||||
* pcibios_fixup_bus
|
||||
* pci_bus_add_device
|
||||
* pci_mmap_page_range
|
||||
*/
|
||||
|
||||
struct pci_controller* pci_ctrl_head;
|
||||
struct pci_controller** pci_ctrl_tail = &pci_ctrl_head;
|
||||
|
||||
static int pci_bus_count;
|
||||
|
||||
/*
|
||||
* We need to avoid collisions with `mirrored' VGA ports
|
||||
* and other strange ISA hardware, so we always want the
|
||||
* addresses to be allocated in the 0x000-0x0ff region
|
||||
* modulo 0x400.
|
||||
*
|
||||
* Why? Because some silly external IO cards only decode
|
||||
* the low 10 bits of the IO address. The 0x00-0xff region
|
||||
* is reserved for motherboard devices that decode all 16
|
||||
* bits, so it's ok to allocate at, say, 0x2800-0x28ff,
|
||||
* but we want to try to avoid allocating at 0x2900-0x2bff
|
||||
* which might have be mirrored at 0x0100-0x03ff..
|
||||
*/
|
||||
resource_size_t
|
||||
pcibios_align_resource(void *data, const struct resource *res,
|
||||
resource_size_t size, resource_size_t align)
|
||||
{
|
||||
struct pci_dev *dev = data;
|
||||
resource_size_t start = res->start;
|
||||
|
||||
if (res->flags & IORESOURCE_IO) {
|
||||
if (size > 0x100) {
|
||||
pr_err("PCI: I/O Region %s/%d too large (%u bytes)\n",
|
||||
pci_name(dev), dev->resource - res,
|
||||
size);
|
||||
}
|
||||
|
||||
if (start & 0x300)
|
||||
start = (start + 0x3ff) & ~0x3ff;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
int
|
||||
pcibios_enable_resources(struct pci_dev *dev, int mask)
|
||||
{
|
||||
u16 cmd, old_cmd;
|
||||
int idx;
|
||||
struct resource *r;
|
||||
|
||||
pci_read_config_word(dev, PCI_COMMAND, &cmd);
|
||||
old_cmd = cmd;
|
||||
for(idx=0; idx<6; idx++) {
|
||||
r = &dev->resource[idx];
|
||||
if (!r->start && r->end) {
|
||||
printk (KERN_ERR "PCI: Device %s not available because "
|
||||
"of resource collisions\n", pci_name(dev));
|
||||
return -EINVAL;
|
||||
}
|
||||
if (r->flags & IORESOURCE_IO)
|
||||
cmd |= PCI_COMMAND_IO;
|
||||
if (r->flags & IORESOURCE_MEM)
|
||||
cmd |= PCI_COMMAND_MEMORY;
|
||||
}
|
||||
if (dev->resource[PCI_ROM_RESOURCE].start)
|
||||
cmd |= PCI_COMMAND_MEMORY;
|
||||
if (cmd != old_cmd) {
|
||||
printk("PCI: Enabling device %s (%04x -> %04x)\n",
|
||||
pci_name(dev), old_cmd, cmd);
|
||||
pci_write_config_word(dev, PCI_COMMAND, cmd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pci_controller * __init pcibios_alloc_controller(void)
|
||||
{
|
||||
struct pci_controller *pci_ctrl;
|
||||
|
||||
pci_ctrl = (struct pci_controller *)alloc_bootmem(sizeof(*pci_ctrl));
|
||||
memset(pci_ctrl, 0, sizeof(struct pci_controller));
|
||||
|
||||
*pci_ctrl_tail = pci_ctrl;
|
||||
pci_ctrl_tail = &pci_ctrl->next;
|
||||
|
||||
return pci_ctrl;
|
||||
}
|
||||
|
||||
static void __init pci_controller_apertures(struct pci_controller *pci_ctrl,
|
||||
struct list_head *resources)
|
||||
{
|
||||
struct resource *res;
|
||||
unsigned long io_offset;
|
||||
int i;
|
||||
|
||||
io_offset = (unsigned long)pci_ctrl->io_space.base;
|
||||
res = &pci_ctrl->io_resource;
|
||||
if (!res->flags) {
|
||||
if (io_offset)
|
||||
printk (KERN_ERR "I/O resource not set for host"
|
||||
" bridge %d\n", pci_ctrl->index);
|
||||
res->start = 0;
|
||||
res->end = IO_SPACE_LIMIT;
|
||||
res->flags = IORESOURCE_IO;
|
||||
}
|
||||
res->start += io_offset;
|
||||
res->end += io_offset;
|
||||
pci_add_resource_offset(resources, res, io_offset);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
res = &pci_ctrl->mem_resources[i];
|
||||
if (!res->flags) {
|
||||
if (i > 0)
|
||||
continue;
|
||||
printk(KERN_ERR "Memory resource not set for "
|
||||
"host bridge %d\n", pci_ctrl->index);
|
||||
res->start = 0;
|
||||
res->end = ~0U;
|
||||
res->flags = IORESOURCE_MEM;
|
||||
}
|
||||
pci_add_resource(resources, res);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init pcibios_init(void)
|
||||
{
|
||||
struct pci_controller *pci_ctrl;
|
||||
struct list_head resources;
|
||||
struct pci_bus *bus;
|
||||
int next_busno = 0;
|
||||
|
||||
printk("PCI: Probing PCI hardware\n");
|
||||
|
||||
/* Scan all of the recorded PCI controllers. */
|
||||
for (pci_ctrl = pci_ctrl_head; pci_ctrl; pci_ctrl = pci_ctrl->next) {
|
||||
pci_ctrl->last_busno = 0xff;
|
||||
INIT_LIST_HEAD(&resources);
|
||||
pci_controller_apertures(pci_ctrl, &resources);
|
||||
bus = pci_scan_root_bus(NULL, pci_ctrl->first_busno,
|
||||
pci_ctrl->ops, pci_ctrl, &resources);
|
||||
pci_ctrl->bus = bus;
|
||||
pci_ctrl->last_busno = bus->busn_res.end;
|
||||
if (next_busno <= pci_ctrl->last_busno)
|
||||
next_busno = pci_ctrl->last_busno+1;
|
||||
}
|
||||
pci_bus_count = next_busno;
|
||||
|
||||
return platform_pcibios_fixup();
|
||||
}
|
||||
|
||||
subsys_initcall(pcibios_init);
|
||||
|
||||
void pcibios_fixup_bus(struct pci_bus *bus)
|
||||
{
|
||||
if (bus->parent) {
|
||||
/* This is a subordinate bridge */
|
||||
pci_read_bridge_bases(bus);
|
||||
}
|
||||
}
|
||||
|
||||
void pcibios_set_master(struct pci_dev *dev)
|
||||
{
|
||||
/* No special bus mastering setup handling */
|
||||
}
|
||||
|
||||
int pcibios_enable_device(struct pci_dev *dev, int mask)
|
||||
{
|
||||
u16 cmd, old_cmd;
|
||||
int idx;
|
||||
struct resource *r;
|
||||
|
||||
pci_read_config_word(dev, PCI_COMMAND, &cmd);
|
||||
old_cmd = cmd;
|
||||
for (idx=0; idx<6; idx++) {
|
||||
r = &dev->resource[idx];
|
||||
if (!r->start && r->end) {
|
||||
printk(KERN_ERR "PCI: Device %s not available because "
|
||||
"of resource collisions\n", pci_name(dev));
|
||||
return -EINVAL;
|
||||
}
|
||||
if (r->flags & IORESOURCE_IO)
|
||||
cmd |= PCI_COMMAND_IO;
|
||||
if (r->flags & IORESOURCE_MEM)
|
||||
cmd |= PCI_COMMAND_MEMORY;
|
||||
}
|
||||
if (cmd != old_cmd) {
|
||||
printk("PCI: Enabling device %s (%04x -> %04x)\n",
|
||||
pci_name(dev), old_cmd, cmd);
|
||||
pci_write_config_word(dev, PCI_COMMAND, cmd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
||||
/*
|
||||
* Return the index of the PCI controller for device pdev.
|
||||
*/
|
||||
|
||||
int
|
||||
pci_controller_num(struct pci_dev *dev)
|
||||
{
|
||||
struct pci_controller *pci_ctrl = (struct pci_controller*) dev->sysdata;
|
||||
return pci_ctrl->index;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
|
||||
/*
|
||||
* Platform support for /proc/bus/pci/X/Y mmap()s,
|
||||
* modelled on the sparc64 implementation by Dave Miller.
|
||||
* -- paulus.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adjust vm_pgoff of VMA such that it is the physical page offset
|
||||
* corresponding to the 32-bit pci bus offset for DEV requested by the user.
|
||||
*
|
||||
* Basically, the user finds the base address for his device which he wishes
|
||||
* to mmap. They read the 32-bit value from the config space base register,
|
||||
* add whatever PAGE_SIZE multiple offset they wish, and feed this into the
|
||||
* offset parameter of mmap on /proc/bus/pci/XXX for that device.
|
||||
*
|
||||
* Returns negative error code on failure, zero on success.
|
||||
*/
|
||||
static __inline__ int
|
||||
__pci_mmap_make_offset(struct pci_dev *dev, struct vm_area_struct *vma,
|
||||
enum pci_mmap_state mmap_state)
|
||||
{
|
||||
struct pci_controller *pci_ctrl = (struct pci_controller*) dev->sysdata;
|
||||
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
|
||||
unsigned long io_offset = 0;
|
||||
int i, res_bit;
|
||||
|
||||
if (pci_ctrl == 0)
|
||||
return -EINVAL; /* should never happen */
|
||||
|
||||
/* If memory, add on the PCI bridge address offset */
|
||||
if (mmap_state == pci_mmap_mem) {
|
||||
res_bit = IORESOURCE_MEM;
|
||||
} else {
|
||||
io_offset = (unsigned long)pci_ctrl->io_space.base;
|
||||
offset += io_offset;
|
||||
res_bit = IORESOURCE_IO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that the offset requested corresponds to one of the
|
||||
* resources of the device.
|
||||
*/
|
||||
for (i = 0; i <= PCI_ROM_RESOURCE; i++) {
|
||||
struct resource *rp = &dev->resource[i];
|
||||
int flags = rp->flags;
|
||||
|
||||
/* treat ROM as memory (should be already) */
|
||||
if (i == PCI_ROM_RESOURCE)
|
||||
flags |= IORESOURCE_MEM;
|
||||
|
||||
/* Active and same type? */
|
||||
if ((flags & res_bit) == 0)
|
||||
continue;
|
||||
|
||||
/* In the range of this resource? */
|
||||
if (offset < (rp->start & PAGE_MASK) || offset > rp->end)
|
||||
continue;
|
||||
|
||||
/* found it! construct the final physical address */
|
||||
if (mmap_state == pci_mmap_io)
|
||||
offset += pci_ctrl->io_space.start - io_offset;
|
||||
vma->vm_pgoff = offset >> PAGE_SHIFT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set vm_page_prot of VMA, as appropriate for this architecture, for a pci
|
||||
* device mapping.
|
||||
*/
|
||||
static __inline__ void
|
||||
__pci_mmap_set_pgprot(struct pci_dev *dev, struct vm_area_struct *vma,
|
||||
enum pci_mmap_state mmap_state, int write_combine)
|
||||
{
|
||||
int prot = pgprot_val(vma->vm_page_prot);
|
||||
|
||||
/* Set to write-through */
|
||||
prot = (prot & _PAGE_CA_MASK) | _PAGE_CA_WT;
|
||||
#if 0
|
||||
if (!write_combine)
|
||||
prot |= _PAGE_WRITETHRU;
|
||||
#endif
|
||||
vma->vm_page_prot = __pgprot(prot);
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform the actual remap of the pages for a PCI device mapping, as
|
||||
* appropriate for this architecture. The region in the process to map
|
||||
* is described by vm_start and vm_end members of VMA, the base physical
|
||||
* address is found in vm_pgoff.
|
||||
* The pci device structure is provided so that architectures may make mapping
|
||||
* decisions on a per-device or per-bus basis.
|
||||
*
|
||||
* Returns a negative error code on failure, zero on success.
|
||||
*/
|
||||
int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma,
|
||||
enum pci_mmap_state mmap_state,
|
||||
int write_combine)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = __pci_mmap_make_offset(dev, vma, mmap_state);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
__pci_mmap_set_pgprot(dev, vma, mmap_state, write_combine);
|
||||
|
||||
ret = io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
|
||||
vma->vm_end - vma->vm_start,vma->vm_page_prot);
|
||||
|
||||
return ret;
|
||||
}
|
46
arch/xtensa/kernel/platform.c
Normal file
46
arch/xtensa/kernel/platform.c
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/platform.c
|
||||
*
|
||||
* Default platform functions.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2005 Tensilica Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/time.h>
|
||||
#include <asm/platform.h>
|
||||
#include <asm/timex.h>
|
||||
#include <asm/param.h> /* HZ */
|
||||
|
||||
#define _F(r,f,a,b) \
|
||||
r __platform_##f a b; \
|
||||
r platform_##f a __attribute__((weak, alias("__platform_"#f)))
|
||||
|
||||
/*
|
||||
* Default functions that are used if no platform specific function is defined.
|
||||
* (Please, refer to include/asm-xtensa/platform.h for more information)
|
||||
*/
|
||||
|
||||
_F(void, setup, (char** cmd), { });
|
||||
_F(void, restart, (void), { while(1); });
|
||||
_F(void, halt, (void), { while(1); });
|
||||
_F(void, power_off, (void), { while(1); });
|
||||
_F(void, idle, (void), { __asm__ __volatile__ ("waiti 0" ::: "memory"); });
|
||||
_F(void, heartbeat, (void), { });
|
||||
_F(int, pcibios_fixup, (void), { return 0; });
|
||||
_F(void, pcibios_init, (void), { });
|
||||
|
||||
#ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT
|
||||
_F(void, calibrate_ccount, (void),
|
||||
{
|
||||
pr_err("ERROR: Cannot calibrate cpu frequency! Assuming 10MHz.\n");
|
||||
ccount_freq = 10 * 1000000UL;
|
||||
});
|
||||
#endif
|
356
arch/xtensa/kernel/process.c
Normal file
356
arch/xtensa/kernel/process.c
Normal file
|
@ -0,0 +1,356 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/process.c
|
||||
*
|
||||
* Xtensa Processor version.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2005 Tensilica Inc.
|
||||
*
|
||||
* Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Marc Gauthier <marc@tensilica.com, marc@alumni.uwaterloo.ca>
|
||||
* Kevin Chea
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/stddef.h>
|
||||
#include <linux/unistd.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/elf.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/prctl.h>
|
||||
#include <linux/init_task.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mqueue.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/rcupdate.h>
|
||||
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/platform.h>
|
||||
#include <asm/mmu.h>
|
||||
#include <asm/irq.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
#include <asm/regs.h>
|
||||
|
||||
extern void ret_from_fork(void);
|
||||
extern void ret_from_kernel_thread(void);
|
||||
|
||||
struct task_struct *current_set[NR_CPUS] = {&init_task, };
|
||||
|
||||
void (*pm_power_off)(void) = NULL;
|
||||
EXPORT_SYMBOL(pm_power_off);
|
||||
|
||||
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
|
||||
void coprocessor_release_all(struct thread_info *ti)
|
||||
{
|
||||
unsigned long cpenable;
|
||||
int i;
|
||||
|
||||
/* Make sure we don't switch tasks during this operation. */
|
||||
|
||||
preempt_disable();
|
||||
|
||||
/* Walk through all cp owners and release it for the requested one. */
|
||||
|
||||
cpenable = ti->cpenable;
|
||||
|
||||
for (i = 0; i < XCHAL_CP_MAX; i++) {
|
||||
if (coprocessor_owner[i] == ti) {
|
||||
coprocessor_owner[i] = 0;
|
||||
cpenable &= ~(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
ti->cpenable = cpenable;
|
||||
coprocessor_clear_cpenable();
|
||||
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
void coprocessor_flush_all(struct thread_info *ti)
|
||||
{
|
||||
unsigned long cpenable;
|
||||
int i;
|
||||
|
||||
preempt_disable();
|
||||
|
||||
cpenable = ti->cpenable;
|
||||
|
||||
for (i = 0; i < XCHAL_CP_MAX; i++) {
|
||||
if ((cpenable & 1) != 0 && coprocessor_owner[i] == ti)
|
||||
coprocessor_flush(ti, i);
|
||||
cpenable >>= 1;
|
||||
}
|
||||
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Powermanagement idle function, if any is provided by the platform.
|
||||
*/
|
||||
void arch_cpu_idle(void)
|
||||
{
|
||||
platform_idle();
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called when the thread calls exit().
|
||||
*/
|
||||
void exit_thread(void)
|
||||
{
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
coprocessor_release_all(current_thread_info());
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Flush thread state. This is called when a thread does an execve()
|
||||
* Note that we flush coprocessor registers for the case execve fails.
|
||||
*/
|
||||
void flush_thread(void)
|
||||
{
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
struct thread_info *ti = current_thread_info();
|
||||
coprocessor_flush_all(ti);
|
||||
coprocessor_release_all(ti);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* this gets called so that we can store coprocessor state into memory and
|
||||
* copy the current task into the new thread.
|
||||
*/
|
||||
int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
|
||||
{
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
coprocessor_flush_all(task_thread_info(src));
|
||||
#endif
|
||||
*dst = *src;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy thread.
|
||||
*
|
||||
* There are two modes in which this function is called:
|
||||
* 1) Userspace thread creation,
|
||||
* regs != NULL, usp_thread_fn is userspace stack pointer.
|
||||
* It is expected to copy parent regs (in case CLONE_VM is not set
|
||||
* in the clone_flags) and set up passed usp in the childregs.
|
||||
* 2) Kernel thread creation,
|
||||
* regs == NULL, usp_thread_fn is the function to run in the new thread
|
||||
* and thread_fn_arg is its parameter.
|
||||
* childregs are not used for the kernel threads.
|
||||
*
|
||||
* The stack layout for the new thread looks like this:
|
||||
*
|
||||
* +------------------------+
|
||||
* | childregs |
|
||||
* +------------------------+ <- thread.sp = sp in dummy-frame
|
||||
* | dummy-frame | (saved in dummy-frame spill-area)
|
||||
* +------------------------+
|
||||
*
|
||||
* We create a dummy frame to return to either ret_from_fork or
|
||||
* ret_from_kernel_thread:
|
||||
* a0 points to ret_from_fork/ret_from_kernel_thread (simulating a call4)
|
||||
* sp points to itself (thread.sp)
|
||||
* a2, a3 are unused for userspace threads,
|
||||
* a2 points to thread_fn, a3 holds thread_fn arg for kernel threads.
|
||||
*
|
||||
* Note: This is a pristine frame, so we don't need any spill region on top of
|
||||
* childregs.
|
||||
*
|
||||
* The fun part: if we're keeping the same VM (i.e. cloning a thread,
|
||||
* not an entire process), we're normally given a new usp, and we CANNOT share
|
||||
* any live address register windows. If we just copy those live frames over,
|
||||
* the two threads (parent and child) will overflow the same frames onto the
|
||||
* parent stack at different times, likely corrupting the parent stack (esp.
|
||||
* if the parent returns from functions that called clone() and calls new
|
||||
* ones, before the child overflows its now old copies of its parent windows).
|
||||
* One solution is to spill windows to the parent stack, but that's fairly
|
||||
* involved. Much simpler to just not copy those live frames across.
|
||||
*/
|
||||
|
||||
int copy_thread(unsigned long clone_flags, unsigned long usp_thread_fn,
|
||||
unsigned long thread_fn_arg, struct task_struct *p)
|
||||
{
|
||||
struct pt_regs *childregs = task_pt_regs(p);
|
||||
|
||||
#if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS)
|
||||
struct thread_info *ti;
|
||||
#endif
|
||||
|
||||
/* Create a call4 dummy-frame: a0 = 0, a1 = childregs. */
|
||||
*((int*)childregs - 3) = (unsigned long)childregs;
|
||||
*((int*)childregs - 4) = 0;
|
||||
|
||||
p->thread.sp = (unsigned long)childregs;
|
||||
|
||||
if (!(p->flags & PF_KTHREAD)) {
|
||||
struct pt_regs *regs = current_pt_regs();
|
||||
unsigned long usp = usp_thread_fn ?
|
||||
usp_thread_fn : regs->areg[1];
|
||||
|
||||
p->thread.ra = MAKE_RA_FOR_CALL(
|
||||
(unsigned long)ret_from_fork, 0x1);
|
||||
|
||||
/* This does not copy all the regs.
|
||||
* In a bout of brilliance or madness,
|
||||
* ARs beyond a0-a15 exist past the end of the struct.
|
||||
*/
|
||||
*childregs = *regs;
|
||||
childregs->areg[1] = usp;
|
||||
childregs->areg[2] = 0;
|
||||
|
||||
/* When sharing memory with the parent thread, the child
|
||||
usually starts on a pristine stack, so we have to reset
|
||||
windowbase, windowstart and wmask.
|
||||
(Note that such a new thread is required to always create
|
||||
an initial call4 frame)
|
||||
The exception is vfork, where the new thread continues to
|
||||
run on the parent's stack until it calls execve. This could
|
||||
be a call8 or call12, which requires a legal stack frame
|
||||
of the previous caller for the overflow handlers to work.
|
||||
(Note that it's always legal to overflow live registers).
|
||||
In this case, ensure to spill at least the stack pointer
|
||||
of that frame. */
|
||||
|
||||
if (clone_flags & CLONE_VM) {
|
||||
/* check that caller window is live and same stack */
|
||||
int len = childregs->wmask & ~0xf;
|
||||
if (regs->areg[1] == usp && len != 0) {
|
||||
int callinc = (regs->areg[0] >> 30) & 3;
|
||||
int caller_ars = XCHAL_NUM_AREGS - callinc * 4;
|
||||
put_user(regs->areg[caller_ars+1],
|
||||
(unsigned __user*)(usp - 12));
|
||||
}
|
||||
childregs->wmask = 1;
|
||||
childregs->windowstart = 1;
|
||||
childregs->windowbase = 0;
|
||||
} else {
|
||||
int len = childregs->wmask & ~0xf;
|
||||
memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4],
|
||||
®s->areg[XCHAL_NUM_AREGS - len/4], len);
|
||||
}
|
||||
|
||||
/* The thread pointer is passed in the '4th argument' (= a5) */
|
||||
if (clone_flags & CLONE_SETTLS)
|
||||
childregs->threadptr = childregs->areg[5];
|
||||
} else {
|
||||
p->thread.ra = MAKE_RA_FOR_CALL(
|
||||
(unsigned long)ret_from_kernel_thread, 1);
|
||||
|
||||
/* pass parameters to ret_from_kernel_thread:
|
||||
* a2 = thread_fn, a3 = thread_fn arg
|
||||
*/
|
||||
*((int *)childregs - 1) = thread_fn_arg;
|
||||
*((int *)childregs - 2) = usp_thread_fn;
|
||||
|
||||
/* Childregs are only used when we're going to userspace
|
||||
* in which case start_thread will set them up.
|
||||
*/
|
||||
}
|
||||
|
||||
#if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS)
|
||||
ti = task_thread_info(p);
|
||||
ti->cpenable = 0;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* These bracket the sleeping functions..
|
||||
*/
|
||||
|
||||
unsigned long get_wchan(struct task_struct *p)
|
||||
{
|
||||
unsigned long sp, pc;
|
||||
unsigned long stack_page = (unsigned long) task_stack_page(p);
|
||||
int count = 0;
|
||||
|
||||
if (!p || p == current || p->state == TASK_RUNNING)
|
||||
return 0;
|
||||
|
||||
sp = p->thread.sp;
|
||||
pc = MAKE_PC_FROM_RA(p->thread.ra, p->thread.sp);
|
||||
|
||||
do {
|
||||
if (sp < stack_page + sizeof(struct task_struct) ||
|
||||
sp >= (stack_page + THREAD_SIZE) ||
|
||||
pc == 0)
|
||||
return 0;
|
||||
if (!in_sched_functions(pc))
|
||||
return pc;
|
||||
|
||||
/* Stack layout: sp-4: ra, sp-3: sp' */
|
||||
|
||||
pc = MAKE_PC_FROM_RA(*(unsigned long*)sp - 4, sp);
|
||||
sp = *(unsigned long *)sp - 3;
|
||||
} while (count++ < 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* xtensa_gregset_t and 'struct pt_regs' are vastly different formats
|
||||
* of processor registers. Besides different ordering,
|
||||
* xtensa_gregset_t contains non-live register information that
|
||||
* 'struct pt_regs' does not. Exception handling (primarily) uses
|
||||
* 'struct pt_regs'. Core files and ptrace use xtensa_gregset_t.
|
||||
*
|
||||
*/
|
||||
|
||||
void xtensa_elf_core_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs)
|
||||
{
|
||||
unsigned long wb, ws, wm;
|
||||
int live, last;
|
||||
|
||||
wb = regs->windowbase;
|
||||
ws = regs->windowstart;
|
||||
wm = regs->wmask;
|
||||
ws = ((ws >> wb) | (ws << (WSBITS - wb))) & ((1 << WSBITS) - 1);
|
||||
|
||||
/* Don't leak any random bits. */
|
||||
|
||||
memset(elfregs, 0, sizeof(*elfregs));
|
||||
|
||||
/* Note: PS.EXCM is not set while user task is running; its
|
||||
* being set in regs->ps is for exception handling convenience.
|
||||
*/
|
||||
|
||||
elfregs->pc = regs->pc;
|
||||
elfregs->ps = (regs->ps & ~(1 << PS_EXCM_BIT));
|
||||
elfregs->lbeg = regs->lbeg;
|
||||
elfregs->lend = regs->lend;
|
||||
elfregs->lcount = regs->lcount;
|
||||
elfregs->sar = regs->sar;
|
||||
elfregs->windowstart = ws;
|
||||
|
||||
live = (wm & 2) ? 4 : (wm & 4) ? 8 : (wm & 8) ? 12 : 16;
|
||||
last = XCHAL_NUM_AREGS - (wm >> 4) * 4;
|
||||
memcpy(elfregs->a, regs->areg, live * 4);
|
||||
memcpy(elfregs->a + last, regs->areg + last, (wm >> 4) * 16);
|
||||
}
|
||||
|
||||
int dump_fpu(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
354
arch/xtensa/kernel/ptrace.c
Normal file
354
arch/xtensa/kernel/ptrace.c
Normal file
|
@ -0,0 +1,354 @@
|
|||
// TODO some minor issues
|
||||
/*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2007 Tensilica Inc.
|
||||
*
|
||||
* Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Scott Foehner<sfoehner@yahoo.com>,
|
||||
* Kevin Chea
|
||||
* Marc Gauthier<marc@tensilica.com> <marc@alumni.uwaterloo.ca>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/signal.h>
|
||||
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/elf.h>
|
||||
#include <asm/coprocessor.h>
|
||||
|
||||
|
||||
void user_enable_single_step(struct task_struct *child)
|
||||
{
|
||||
child->ptrace |= PT_SINGLESTEP;
|
||||
}
|
||||
|
||||
void user_disable_single_step(struct task_struct *child)
|
||||
{
|
||||
child->ptrace &= ~PT_SINGLESTEP;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by kernel/ptrace.c when detaching to disable single stepping.
|
||||
*/
|
||||
|
||||
void ptrace_disable(struct task_struct *child)
|
||||
{
|
||||
/* Nothing to do.. */
|
||||
}
|
||||
|
||||
int ptrace_getregs(struct task_struct *child, void __user *uregs)
|
||||
{
|
||||
struct pt_regs *regs = task_pt_regs(child);
|
||||
xtensa_gregset_t __user *gregset = uregs;
|
||||
unsigned long wb = regs->windowbase;
|
||||
int i;
|
||||
|
||||
if (!access_ok(VERIFY_WRITE, uregs, sizeof(xtensa_gregset_t)))
|
||||
return -EIO;
|
||||
|
||||
__put_user(regs->pc, &gregset->pc);
|
||||
__put_user(regs->ps & ~(1 << PS_EXCM_BIT), &gregset->ps);
|
||||
__put_user(regs->lbeg, &gregset->lbeg);
|
||||
__put_user(regs->lend, &gregset->lend);
|
||||
__put_user(regs->lcount, &gregset->lcount);
|
||||
__put_user(regs->windowstart, &gregset->windowstart);
|
||||
__put_user(regs->windowbase, &gregset->windowbase);
|
||||
__put_user(regs->threadptr, &gregset->threadptr);
|
||||
|
||||
for (i = 0; i < XCHAL_NUM_AREGS; i++)
|
||||
__put_user(regs->areg[i],
|
||||
gregset->a + ((wb * 4 + i) % XCHAL_NUM_AREGS));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ptrace_setregs(struct task_struct *child, void __user *uregs)
|
||||
{
|
||||
struct pt_regs *regs = task_pt_regs(child);
|
||||
xtensa_gregset_t *gregset = uregs;
|
||||
const unsigned long ps_mask = PS_CALLINC_MASK | PS_OWB_MASK;
|
||||
unsigned long ps;
|
||||
unsigned long wb, ws;
|
||||
|
||||
if (!access_ok(VERIFY_WRITE, uregs, sizeof(xtensa_gregset_t)))
|
||||
return -EIO;
|
||||
|
||||
__get_user(regs->pc, &gregset->pc);
|
||||
__get_user(ps, &gregset->ps);
|
||||
__get_user(regs->lbeg, &gregset->lbeg);
|
||||
__get_user(regs->lend, &gregset->lend);
|
||||
__get_user(regs->lcount, &gregset->lcount);
|
||||
__get_user(ws, &gregset->windowstart);
|
||||
__get_user(wb, &gregset->windowbase);
|
||||
__get_user(regs->threadptr, &gregset->threadptr);
|
||||
|
||||
regs->ps = (regs->ps & ~ps_mask) | (ps & ps_mask) | (1 << PS_EXCM_BIT);
|
||||
|
||||
if (wb >= XCHAL_NUM_AREGS / 4)
|
||||
return -EFAULT;
|
||||
|
||||
if (wb != regs->windowbase || ws != regs->windowstart) {
|
||||
unsigned long rotws, wmask;
|
||||
|
||||
rotws = (((ws | (ws << WSBITS)) >> wb) &
|
||||
((1 << WSBITS) - 1)) & ~1;
|
||||
wmask = ((rotws ? WSBITS + 1 - ffs(rotws) : 0) << 4) |
|
||||
(rotws & 0xF) | 1;
|
||||
regs->windowbase = wb;
|
||||
regs->windowstart = ws;
|
||||
regs->wmask = wmask;
|
||||
}
|
||||
|
||||
if (wb != 0 && __copy_from_user(regs->areg + XCHAL_NUM_AREGS - wb * 4,
|
||||
gregset->a, wb * 16))
|
||||
return -EFAULT;
|
||||
|
||||
if (__copy_from_user(regs->areg, gregset->a + wb * 4,
|
||||
(WSBITS - wb) * 16))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ptrace_getxregs(struct task_struct *child, void __user *uregs)
|
||||
{
|
||||
struct pt_regs *regs = task_pt_regs(child);
|
||||
struct thread_info *ti = task_thread_info(child);
|
||||
elf_xtregs_t __user *xtregs = uregs;
|
||||
int ret = 0;
|
||||
|
||||
if (!access_ok(VERIFY_WRITE, uregs, sizeof(elf_xtregs_t)))
|
||||
return -EIO;
|
||||
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
/* Flush all coprocessor registers to memory. */
|
||||
coprocessor_flush_all(ti);
|
||||
ret |= __copy_to_user(&xtregs->cp0, &ti->xtregs_cp,
|
||||
sizeof(xtregs_coprocessor_t));
|
||||
#endif
|
||||
ret |= __copy_to_user(&xtregs->opt, ®s->xtregs_opt,
|
||||
sizeof(xtregs->opt));
|
||||
ret |= __copy_to_user(&xtregs->user,&ti->xtregs_user,
|
||||
sizeof(xtregs->user));
|
||||
|
||||
return ret ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
int ptrace_setxregs(struct task_struct *child, void __user *uregs)
|
||||
{
|
||||
struct thread_info *ti = task_thread_info(child);
|
||||
struct pt_regs *regs = task_pt_regs(child);
|
||||
elf_xtregs_t *xtregs = uregs;
|
||||
int ret = 0;
|
||||
|
||||
if (!access_ok(VERIFY_READ, uregs, sizeof(elf_xtregs_t)))
|
||||
return -EFAULT;
|
||||
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
/* Flush all coprocessors before we overwrite them. */
|
||||
coprocessor_flush_all(ti);
|
||||
coprocessor_release_all(ti);
|
||||
|
||||
ret |= __copy_from_user(&ti->xtregs_cp, &xtregs->cp0,
|
||||
sizeof(xtregs_coprocessor_t));
|
||||
#endif
|
||||
ret |= __copy_from_user(®s->xtregs_opt, &xtregs->opt,
|
||||
sizeof(xtregs->opt));
|
||||
ret |= __copy_from_user(&ti->xtregs_user, &xtregs->user,
|
||||
sizeof(xtregs->user));
|
||||
|
||||
return ret ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
int ptrace_peekusr(struct task_struct *child, long regno, long __user *ret)
|
||||
{
|
||||
struct pt_regs *regs;
|
||||
unsigned long tmp;
|
||||
|
||||
regs = task_pt_regs(child);
|
||||
tmp = 0; /* Default return value. */
|
||||
|
||||
switch(regno) {
|
||||
|
||||
case REG_AR_BASE ... REG_AR_BASE + XCHAL_NUM_AREGS - 1:
|
||||
tmp = regs->areg[regno - REG_AR_BASE];
|
||||
break;
|
||||
|
||||
case REG_A_BASE ... REG_A_BASE + 15:
|
||||
tmp = regs->areg[regno - REG_A_BASE];
|
||||
break;
|
||||
|
||||
case REG_PC:
|
||||
tmp = regs->pc;
|
||||
break;
|
||||
|
||||
case REG_PS:
|
||||
/* Note: PS.EXCM is not set while user task is running;
|
||||
* its being set in regs is for exception handling
|
||||
* convenience. */
|
||||
tmp = (regs->ps & ~(1 << PS_EXCM_BIT));
|
||||
break;
|
||||
|
||||
case REG_WB:
|
||||
break; /* tmp = 0 */
|
||||
|
||||
case REG_WS:
|
||||
{
|
||||
unsigned long wb = regs->windowbase;
|
||||
unsigned long ws = regs->windowstart;
|
||||
tmp = ((ws>>wb) | (ws<<(WSBITS-wb))) & ((1<<WSBITS)-1);
|
||||
break;
|
||||
}
|
||||
case REG_LBEG:
|
||||
tmp = regs->lbeg;
|
||||
break;
|
||||
|
||||
case REG_LEND:
|
||||
tmp = regs->lend;
|
||||
break;
|
||||
|
||||
case REG_LCOUNT:
|
||||
tmp = regs->lcount;
|
||||
break;
|
||||
|
||||
case REG_SAR:
|
||||
tmp = regs->sar;
|
||||
break;
|
||||
|
||||
case SYSCALL_NR:
|
||||
tmp = regs->syscall;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
return put_user(tmp, ret);
|
||||
}
|
||||
|
||||
int ptrace_pokeusr(struct task_struct *child, long regno, long val)
|
||||
{
|
||||
struct pt_regs *regs;
|
||||
regs = task_pt_regs(child);
|
||||
|
||||
switch (regno) {
|
||||
case REG_AR_BASE ... REG_AR_BASE + XCHAL_NUM_AREGS - 1:
|
||||
regs->areg[regno - REG_AR_BASE] = val;
|
||||
break;
|
||||
|
||||
case REG_A_BASE ... REG_A_BASE + 15:
|
||||
regs->areg[regno - REG_A_BASE] = val;
|
||||
break;
|
||||
|
||||
case REG_PC:
|
||||
regs->pc = val;
|
||||
break;
|
||||
|
||||
case SYSCALL_NR:
|
||||
regs->syscall = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
long arch_ptrace(struct task_struct *child, long request,
|
||||
unsigned long addr, unsigned long data)
|
||||
{
|
||||
int ret = -EPERM;
|
||||
void __user *datap = (void __user *) data;
|
||||
|
||||
switch (request) {
|
||||
case PTRACE_PEEKTEXT: /* read word at location addr. */
|
||||
case PTRACE_PEEKDATA:
|
||||
ret = generic_ptrace_peekdata(child, addr, data);
|
||||
break;
|
||||
|
||||
case PTRACE_PEEKUSR: /* read register specified by addr. */
|
||||
ret = ptrace_peekusr(child, addr, datap);
|
||||
break;
|
||||
|
||||
case PTRACE_POKETEXT: /* write the word at location addr. */
|
||||
case PTRACE_POKEDATA:
|
||||
ret = generic_ptrace_pokedata(child, addr, data);
|
||||
break;
|
||||
|
||||
case PTRACE_POKEUSR: /* write register specified by addr. */
|
||||
ret = ptrace_pokeusr(child, addr, data);
|
||||
break;
|
||||
|
||||
case PTRACE_GETREGS:
|
||||
ret = ptrace_getregs(child, datap);
|
||||
break;
|
||||
|
||||
case PTRACE_SETREGS:
|
||||
ret = ptrace_setregs(child, datap);
|
||||
break;
|
||||
|
||||
case PTRACE_GETXTREGS:
|
||||
ret = ptrace_getxregs(child, datap);
|
||||
break;
|
||||
|
||||
case PTRACE_SETXTREGS:
|
||||
ret = ptrace_setxregs(child, datap);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = ptrace_request(child, request, addr, data);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void do_syscall_trace(void)
|
||||
{
|
||||
/*
|
||||
* The 0x80 provides a way for the tracing parent to distinguish
|
||||
* between a syscall stop and SIGTRAP delivery
|
||||
*/
|
||||
ptrace_notify(SIGTRAP|((current->ptrace & PT_TRACESYSGOOD) ? 0x80 : 0));
|
||||
|
||||
/*
|
||||
* this isn't the same as continuing with a signal, but it will do
|
||||
* for normal use. strace only continues with a signal if the
|
||||
* stopping signal is not SIGTRAP. -brl
|
||||
*/
|
||||
if (current->exit_code) {
|
||||
send_sig(current->exit_code, current, 1);
|
||||
current->exit_code = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void do_syscall_trace_enter(struct pt_regs *regs)
|
||||
{
|
||||
if (test_thread_flag(TIF_SYSCALL_TRACE)
|
||||
&& (current->ptrace & PT_PTRACED))
|
||||
do_syscall_trace();
|
||||
|
||||
#if 0
|
||||
audit_syscall_entry(...);
|
||||
#endif
|
||||
}
|
||||
|
||||
void do_syscall_trace_leave(struct pt_regs *regs)
|
||||
{
|
||||
if ((test_thread_flag(TIF_SYSCALL_TRACE))
|
||||
&& (current->ptrace & PT_PTRACED))
|
||||
do_syscall_trace();
|
||||
}
|
731
arch/xtensa/kernel/setup.c
Normal file
731
arch/xtensa/kernel/setup.c
Normal file
|
@ -0,0 +1,731 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/setup.c
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 1995 Linus Torvalds
|
||||
* Copyright (C) 2001 - 2005 Tensilica Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
* Kevin Chea
|
||||
* Marc Gauthier<marc@tensilica.com> <marc@alumni.uwaterloo.ca>
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/screen_info.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/percpu.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/of_fdt.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE)
|
||||
# include <linux/console.h>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_RTC
|
||||
# include <linux/timex.h>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
# include <linux/seq_file.h>
|
||||
#endif
|
||||
|
||||
#include <asm/bootparam.h>
|
||||
#include <asm/mmu_context.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/timex.h>
|
||||
#include <asm/platform.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/param.h>
|
||||
#include <asm/traps.h>
|
||||
#include <asm/smp.h>
|
||||
#include <asm/sysmem.h>
|
||||
|
||||
#include <platform/hardware.h>
|
||||
|
||||
#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE)
|
||||
struct screen_info screen_info = { 0, 24, 0, 0, 0, 80, 0, 0, 0, 24, 1, 16};
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BLK_DEV_FD
|
||||
extern struct fd_ops no_fd_ops;
|
||||
struct fd_ops *fd_ops;
|
||||
#endif
|
||||
|
||||
extern struct rtc_ops no_rtc_ops;
|
||||
struct rtc_ops *rtc_ops;
|
||||
|
||||
#ifdef CONFIG_BLK_DEV_INITRD
|
||||
extern unsigned long initrd_start;
|
||||
extern unsigned long initrd_end;
|
||||
int initrd_is_mapped = 0;
|
||||
extern int initrd_below_start_ok;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
void *dtb_start = __dtb_start;
|
||||
#endif
|
||||
|
||||
unsigned char aux_device_present;
|
||||
extern unsigned long loops_per_jiffy;
|
||||
|
||||
/* Command line specified as configuration option. */
|
||||
|
||||
static char __initdata command_line[COMMAND_LINE_SIZE];
|
||||
|
||||
#ifdef CONFIG_CMDLINE_BOOL
|
||||
static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Boot parameter parsing.
|
||||
*
|
||||
* The Xtensa port uses a list of variable-sized tags to pass data to
|
||||
* the kernel. The first tag must be a BP_TAG_FIRST tag for the list
|
||||
* to be recognised. The list is terminated with a zero-sized
|
||||
* BP_TAG_LAST tag.
|
||||
*/
|
||||
|
||||
typedef struct tagtable {
|
||||
u32 tag;
|
||||
int (*parse)(const bp_tag_t*);
|
||||
} tagtable_t;
|
||||
|
||||
#define __tagtable(tag, fn) static tagtable_t __tagtable_##fn \
|
||||
__attribute__((used, section(".taglist"))) = { tag, fn }
|
||||
|
||||
/* parse current tag */
|
||||
|
||||
static int __init parse_tag_mem(const bp_tag_t *tag)
|
||||
{
|
||||
struct bp_meminfo *mi = (struct bp_meminfo *)(tag->data);
|
||||
|
||||
if (mi->type != MEMORY_TYPE_CONVENTIONAL)
|
||||
return -1;
|
||||
|
||||
return add_sysmem_bank(mi->start, mi->end);
|
||||
}
|
||||
|
||||
__tagtable(BP_TAG_MEMORY, parse_tag_mem);
|
||||
|
||||
#ifdef CONFIG_BLK_DEV_INITRD
|
||||
|
||||
static int __init parse_tag_initrd(const bp_tag_t* tag)
|
||||
{
|
||||
struct bp_meminfo *mi = (struct bp_meminfo *)(tag->data);
|
||||
|
||||
initrd_start = (unsigned long)__va(mi->start);
|
||||
initrd_end = (unsigned long)__va(mi->end);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__tagtable(BP_TAG_INITRD, parse_tag_initrd);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
|
||||
static int __init parse_tag_fdt(const bp_tag_t *tag)
|
||||
{
|
||||
dtb_start = __va(tag->data[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
__tagtable(BP_TAG_FDT, parse_tag_fdt);
|
||||
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
#endif /* CONFIG_BLK_DEV_INITRD */
|
||||
|
||||
static int __init parse_tag_cmdline(const bp_tag_t* tag)
|
||||
{
|
||||
strlcpy(command_line, (char *)(tag->data), COMMAND_LINE_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
__tagtable(BP_TAG_COMMAND_LINE, parse_tag_cmdline);
|
||||
|
||||
static int __init parse_bootparam(const bp_tag_t* tag)
|
||||
{
|
||||
extern tagtable_t __tagtable_begin, __tagtable_end;
|
||||
tagtable_t *t;
|
||||
|
||||
/* Boot parameters must start with a BP_TAG_FIRST tag. */
|
||||
|
||||
if (tag->id != BP_TAG_FIRST) {
|
||||
printk(KERN_WARNING "Invalid boot parameters!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
tag = (bp_tag_t*)((unsigned long)tag + sizeof(bp_tag_t) + tag->size);
|
||||
|
||||
/* Parse all tags. */
|
||||
|
||||
while (tag != NULL && tag->id != BP_TAG_LAST) {
|
||||
for (t = &__tagtable_begin; t < &__tagtable_end; t++) {
|
||||
if (tag->id == t->tag) {
|
||||
t->parse(tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (t == &__tagtable_end)
|
||||
printk(KERN_WARNING "Ignoring tag "
|
||||
"0x%08x\n", tag->id);
|
||||
tag = (bp_tag_t*)((unsigned long)(tag + 1) + tag->size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
bool __initdata dt_memory_scan = false;
|
||||
|
||||
#if XCHAL_HAVE_PTP_MMU && XCHAL_HAVE_SPANNING_WAY
|
||||
unsigned long xtensa_kio_paddr = XCHAL_KIO_DEFAULT_PADDR;
|
||||
EXPORT_SYMBOL(xtensa_kio_paddr);
|
||||
|
||||
static int __init xtensa_dt_io_area(unsigned long node, const char *uname,
|
||||
int depth, void *data)
|
||||
{
|
||||
const __be32 *ranges;
|
||||
int len;
|
||||
|
||||
if (depth > 1)
|
||||
return 0;
|
||||
|
||||
if (!of_flat_dt_is_compatible(node, "simple-bus"))
|
||||
return 0;
|
||||
|
||||
ranges = of_get_flat_dt_prop(node, "ranges", &len);
|
||||
if (!ranges)
|
||||
return 1;
|
||||
if (len == 0)
|
||||
return 1;
|
||||
|
||||
xtensa_kio_paddr = of_read_ulong(ranges+1, 1);
|
||||
/* round down to nearest 256MB boundary */
|
||||
xtensa_kio_paddr &= 0xf0000000;
|
||||
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
static int __init xtensa_dt_io_area(unsigned long node, const char *uname,
|
||||
int depth, void *data)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void __init early_init_dt_add_memory_arch(u64 base, u64 size)
|
||||
{
|
||||
if (!dt_memory_scan)
|
||||
return;
|
||||
|
||||
size &= PAGE_MASK;
|
||||
add_sysmem_bank(base, base + size);
|
||||
}
|
||||
|
||||
void * __init early_init_dt_alloc_memory_arch(u64 size, u64 align)
|
||||
{
|
||||
return __alloc_bootmem(size, align, 0);
|
||||
}
|
||||
|
||||
void __init early_init_devtree(void *params)
|
||||
{
|
||||
if (sysmem.nr_banks == 0)
|
||||
dt_memory_scan = true;
|
||||
|
||||
early_init_dt_scan(params);
|
||||
of_scan_flat_dt(xtensa_dt_io_area, NULL);
|
||||
|
||||
if (!command_line[0])
|
||||
strlcpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
|
||||
}
|
||||
|
||||
static int __init xtensa_device_probe(void)
|
||||
{
|
||||
of_clk_init(NULL);
|
||||
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
device_initcall(xtensa_device_probe);
|
||||
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
/*
|
||||
* Initialize architecture. (Early stage)
|
||||
*/
|
||||
|
||||
void __init init_arch(bp_tag_t *bp_start)
|
||||
{
|
||||
/* Parse boot parameters */
|
||||
|
||||
if (bp_start)
|
||||
parse_bootparam(bp_start);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
early_init_devtree(dtb_start);
|
||||
#endif
|
||||
|
||||
if (sysmem.nr_banks == 0) {
|
||||
add_sysmem_bank(PLATFORM_DEFAULT_MEM_START,
|
||||
PLATFORM_DEFAULT_MEM_START +
|
||||
PLATFORM_DEFAULT_MEM_SIZE);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CMDLINE_BOOL
|
||||
if (!command_line[0])
|
||||
strlcpy(command_line, default_command_line, COMMAND_LINE_SIZE);
|
||||
#endif
|
||||
|
||||
/* Early hook for platforms */
|
||||
|
||||
platform_init(bp_start);
|
||||
|
||||
/* Initialize MMU. */
|
||||
|
||||
init_mmu();
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize system. Setup memory and reserve regions.
|
||||
*/
|
||||
|
||||
extern char _end;
|
||||
extern char _stext;
|
||||
extern char _WindowVectors_text_start;
|
||||
extern char _WindowVectors_text_end;
|
||||
extern char _DebugInterruptVector_literal_start;
|
||||
extern char _DebugInterruptVector_text_end;
|
||||
extern char _KernelExceptionVector_literal_start;
|
||||
extern char _KernelExceptionVector_text_end;
|
||||
extern char _UserExceptionVector_literal_start;
|
||||
extern char _UserExceptionVector_text_end;
|
||||
extern char _DoubleExceptionVector_literal_start;
|
||||
extern char _DoubleExceptionVector_text_end;
|
||||
#if XCHAL_EXCM_LEVEL >= 2
|
||||
extern char _Level2InterruptVector_text_start;
|
||||
extern char _Level2InterruptVector_text_end;
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 3
|
||||
extern char _Level3InterruptVector_text_start;
|
||||
extern char _Level3InterruptVector_text_end;
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 4
|
||||
extern char _Level4InterruptVector_text_start;
|
||||
extern char _Level4InterruptVector_text_end;
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 5
|
||||
extern char _Level5InterruptVector_text_start;
|
||||
extern char _Level5InterruptVector_text_end;
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 6
|
||||
extern char _Level6InterruptVector_text_start;
|
||||
extern char _Level6InterruptVector_text_end;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#ifdef CONFIG_S32C1I_SELFTEST
|
||||
#if XCHAL_HAVE_S32C1I
|
||||
|
||||
static int __initdata rcw_word, rcw_probe_pc, rcw_exc;
|
||||
|
||||
/*
|
||||
* Basic atomic compare-and-swap, that records PC of S32C1I for probing.
|
||||
*
|
||||
* If *v == cmp, set *v = set. Return previous *v.
|
||||
*/
|
||||
static inline int probed_compare_swap(int *v, int cmp, int set)
|
||||
{
|
||||
int tmp;
|
||||
|
||||
__asm__ __volatile__(
|
||||
" movi %1, 1f\n"
|
||||
" s32i %1, %4, 0\n"
|
||||
" wsr %2, scompare1\n"
|
||||
"1: s32c1i %0, %3, 0\n"
|
||||
: "=a" (set), "=&a" (tmp)
|
||||
: "a" (cmp), "a" (v), "a" (&rcw_probe_pc), "0" (set)
|
||||
: "memory"
|
||||
);
|
||||
return set;
|
||||
}
|
||||
|
||||
/* Handle probed exception */
|
||||
|
||||
static void __init do_probed_exception(struct pt_regs *regs,
|
||||
unsigned long exccause)
|
||||
{
|
||||
if (regs->pc == rcw_probe_pc) { /* exception on s32c1i ? */
|
||||
regs->pc += 3; /* skip the s32c1i instruction */
|
||||
rcw_exc = exccause;
|
||||
} else {
|
||||
do_unhandled(regs, exccause);
|
||||
}
|
||||
}
|
||||
|
||||
/* Simple test of S32C1I (soc bringup assist) */
|
||||
|
||||
static int __init check_s32c1i(void)
|
||||
{
|
||||
int n, cause1, cause2;
|
||||
void *handbus, *handdata, *handaddr; /* temporarily saved handlers */
|
||||
|
||||
rcw_probe_pc = 0;
|
||||
handbus = trap_set_handler(EXCCAUSE_LOAD_STORE_ERROR,
|
||||
do_probed_exception);
|
||||
handdata = trap_set_handler(EXCCAUSE_LOAD_STORE_DATA_ERROR,
|
||||
do_probed_exception);
|
||||
handaddr = trap_set_handler(EXCCAUSE_LOAD_STORE_ADDR_ERROR,
|
||||
do_probed_exception);
|
||||
|
||||
/* First try an S32C1I that does not store: */
|
||||
rcw_exc = 0;
|
||||
rcw_word = 1;
|
||||
n = probed_compare_swap(&rcw_word, 0, 2);
|
||||
cause1 = rcw_exc;
|
||||
|
||||
/* took exception? */
|
||||
if (cause1 != 0) {
|
||||
/* unclean exception? */
|
||||
if (n != 2 || rcw_word != 1)
|
||||
panic("S32C1I exception error");
|
||||
} else if (rcw_word != 1 || n != 1) {
|
||||
panic("S32C1I compare error");
|
||||
}
|
||||
|
||||
/* Then an S32C1I that stores: */
|
||||
rcw_exc = 0;
|
||||
rcw_word = 0x1234567;
|
||||
n = probed_compare_swap(&rcw_word, 0x1234567, 0xabcde);
|
||||
cause2 = rcw_exc;
|
||||
|
||||
if (cause2 != 0) {
|
||||
/* unclean exception? */
|
||||
if (n != 0xabcde || rcw_word != 0x1234567)
|
||||
panic("S32C1I exception error (b)");
|
||||
} else if (rcw_word != 0xabcde || n != 0x1234567) {
|
||||
panic("S32C1I store error");
|
||||
}
|
||||
|
||||
/* Verify consistency of exceptions: */
|
||||
if (cause1 || cause2) {
|
||||
pr_warn("S32C1I took exception %d, %d\n", cause1, cause2);
|
||||
/* If emulation of S32C1I upon bus error gets implemented,
|
||||
we can get rid of this panic for single core (not SMP) */
|
||||
panic("S32C1I exceptions not currently supported");
|
||||
}
|
||||
if (cause1 != cause2)
|
||||
panic("inconsistent S32C1I exceptions");
|
||||
|
||||
trap_set_handler(EXCCAUSE_LOAD_STORE_ERROR, handbus);
|
||||
trap_set_handler(EXCCAUSE_LOAD_STORE_DATA_ERROR, handdata);
|
||||
trap_set_handler(EXCCAUSE_LOAD_STORE_ADDR_ERROR, handaddr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* XCHAL_HAVE_S32C1I */
|
||||
|
||||
/* This condition should not occur with a commercially deployed processor.
|
||||
Display reminder for early engr test or demo chips / FPGA bitstreams */
|
||||
static int __init check_s32c1i(void)
|
||||
{
|
||||
pr_warn("Processor configuration lacks atomic compare-and-swap support!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* XCHAL_HAVE_S32C1I */
|
||||
early_initcall(check_s32c1i);
|
||||
#endif /* CONFIG_S32C1I_SELFTEST */
|
||||
|
||||
|
||||
void __init setup_arch(char **cmdline_p)
|
||||
{
|
||||
strlcpy(boot_command_line, command_line, COMMAND_LINE_SIZE);
|
||||
*cmdline_p = command_line;
|
||||
|
||||
/* Reserve some memory regions */
|
||||
|
||||
#ifdef CONFIG_BLK_DEV_INITRD
|
||||
if (initrd_start < initrd_end) {
|
||||
initrd_is_mapped = mem_reserve(__pa(initrd_start),
|
||||
__pa(initrd_end), 0) == 0;
|
||||
initrd_below_start_ok = 1;
|
||||
} else {
|
||||
initrd_start = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
mem_reserve(__pa(&_stext),__pa(&_end), 1);
|
||||
|
||||
mem_reserve(__pa(&_WindowVectors_text_start),
|
||||
__pa(&_WindowVectors_text_end), 0);
|
||||
|
||||
mem_reserve(__pa(&_DebugInterruptVector_literal_start),
|
||||
__pa(&_DebugInterruptVector_text_end), 0);
|
||||
|
||||
mem_reserve(__pa(&_KernelExceptionVector_literal_start),
|
||||
__pa(&_KernelExceptionVector_text_end), 0);
|
||||
|
||||
mem_reserve(__pa(&_UserExceptionVector_literal_start),
|
||||
__pa(&_UserExceptionVector_text_end), 0);
|
||||
|
||||
mem_reserve(__pa(&_DoubleExceptionVector_literal_start),
|
||||
__pa(&_DoubleExceptionVector_text_end), 0);
|
||||
|
||||
#if XCHAL_EXCM_LEVEL >= 2
|
||||
mem_reserve(__pa(&_Level2InterruptVector_text_start),
|
||||
__pa(&_Level2InterruptVector_text_end), 0);
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 3
|
||||
mem_reserve(__pa(&_Level3InterruptVector_text_start),
|
||||
__pa(&_Level3InterruptVector_text_end), 0);
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 4
|
||||
mem_reserve(__pa(&_Level4InterruptVector_text_start),
|
||||
__pa(&_Level4InterruptVector_text_end), 0);
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 5
|
||||
mem_reserve(__pa(&_Level5InterruptVector_text_start),
|
||||
__pa(&_Level5InterruptVector_text_end), 0);
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 6
|
||||
mem_reserve(__pa(&_Level6InterruptVector_text_start),
|
||||
__pa(&_Level6InterruptVector_text_end), 0);
|
||||
#endif
|
||||
|
||||
parse_early_param();
|
||||
bootmem_init();
|
||||
|
||||
unflatten_and_copy_device_tree();
|
||||
|
||||
platform_setup(cmdline_p);
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
smp_init_cpus();
|
||||
#endif
|
||||
|
||||
paging_init();
|
||||
zones_init();
|
||||
|
||||
#ifdef CONFIG_VT
|
||||
# if defined(CONFIG_VGA_CONSOLE)
|
||||
conswitchp = &vga_con;
|
||||
# elif defined(CONFIG_DUMMY_CONSOLE)
|
||||
conswitchp = &dummy_con;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
platform_pcibios_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
static DEFINE_PER_CPU(struct cpu, cpu_data);
|
||||
|
||||
static int __init topology_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for_each_possible_cpu(i) {
|
||||
struct cpu *cpu = &per_cpu(cpu_data, i);
|
||||
cpu->hotpluggable = !!i;
|
||||
register_cpu(cpu, i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
subsys_initcall(topology_init);
|
||||
|
||||
void machine_restart(char * cmd)
|
||||
{
|
||||
platform_restart();
|
||||
}
|
||||
|
||||
void machine_halt(void)
|
||||
{
|
||||
platform_halt();
|
||||
while (1);
|
||||
}
|
||||
|
||||
void machine_power_off(void)
|
||||
{
|
||||
platform_power_off();
|
||||
while (1);
|
||||
}
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
||||
/*
|
||||
* Display some core information through /proc/cpuinfo.
|
||||
*/
|
||||
|
||||
static int
|
||||
c_show(struct seq_file *f, void *slot)
|
||||
{
|
||||
char buf[NR_CPUS * 5];
|
||||
|
||||
cpulist_scnprintf(buf, sizeof(buf), cpu_online_mask);
|
||||
/* high-level stuff */
|
||||
seq_printf(f, "CPU count\t: %u\n"
|
||||
"CPU list\t: %s\n"
|
||||
"vendor_id\t: Tensilica\n"
|
||||
"model\t\t: Xtensa " XCHAL_HW_VERSION_NAME "\n"
|
||||
"core ID\t\t: " XCHAL_CORE_ID "\n"
|
||||
"build ID\t: 0x%x\n"
|
||||
"byte order\t: %s\n"
|
||||
"cpu MHz\t\t: %lu.%02lu\n"
|
||||
"bogomips\t: %lu.%02lu\n",
|
||||
num_online_cpus(),
|
||||
buf,
|
||||
XCHAL_BUILD_UNIQUE_ID,
|
||||
XCHAL_HAVE_BE ? "big" : "little",
|
||||
ccount_freq/1000000,
|
||||
(ccount_freq/10000) % 100,
|
||||
loops_per_jiffy/(500000/HZ),
|
||||
(loops_per_jiffy/(5000/HZ)) % 100);
|
||||
|
||||
seq_printf(f,"flags\t\t: "
|
||||
#if XCHAL_HAVE_NMI
|
||||
"nmi "
|
||||
#endif
|
||||
#if XCHAL_HAVE_DEBUG
|
||||
"debug "
|
||||
# if XCHAL_HAVE_OCD
|
||||
"ocd "
|
||||
# endif
|
||||
#endif
|
||||
#if XCHAL_HAVE_DENSITY
|
||||
"density "
|
||||
#endif
|
||||
#if XCHAL_HAVE_BOOLEANS
|
||||
"boolean "
|
||||
#endif
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
"loop "
|
||||
#endif
|
||||
#if XCHAL_HAVE_NSA
|
||||
"nsa "
|
||||
#endif
|
||||
#if XCHAL_HAVE_MINMAX
|
||||
"minmax "
|
||||
#endif
|
||||
#if XCHAL_HAVE_SEXT
|
||||
"sext "
|
||||
#endif
|
||||
#if XCHAL_HAVE_CLAMPS
|
||||
"clamps "
|
||||
#endif
|
||||
#if XCHAL_HAVE_MAC16
|
||||
"mac16 "
|
||||
#endif
|
||||
#if XCHAL_HAVE_MUL16
|
||||
"mul16 "
|
||||
#endif
|
||||
#if XCHAL_HAVE_MUL32
|
||||
"mul32 "
|
||||
#endif
|
||||
#if XCHAL_HAVE_MUL32_HIGH
|
||||
"mul32h "
|
||||
#endif
|
||||
#if XCHAL_HAVE_FP
|
||||
"fpu "
|
||||
#endif
|
||||
#if XCHAL_HAVE_S32C1I
|
||||
"s32c1i "
|
||||
#endif
|
||||
"\n");
|
||||
|
||||
/* Registers. */
|
||||
seq_printf(f,"physical aregs\t: %d\n"
|
||||
"misc regs\t: %d\n"
|
||||
"ibreak\t\t: %d\n"
|
||||
"dbreak\t\t: %d\n",
|
||||
XCHAL_NUM_AREGS,
|
||||
XCHAL_NUM_MISC_REGS,
|
||||
XCHAL_NUM_IBREAK,
|
||||
XCHAL_NUM_DBREAK);
|
||||
|
||||
|
||||
/* Interrupt. */
|
||||
seq_printf(f,"num ints\t: %d\n"
|
||||
"ext ints\t: %d\n"
|
||||
"int levels\t: %d\n"
|
||||
"timers\t\t: %d\n"
|
||||
"debug level\t: %d\n",
|
||||
XCHAL_NUM_INTERRUPTS,
|
||||
XCHAL_NUM_EXTINTERRUPTS,
|
||||
XCHAL_NUM_INTLEVELS,
|
||||
XCHAL_NUM_TIMERS,
|
||||
XCHAL_DEBUGLEVEL);
|
||||
|
||||
/* Cache */
|
||||
seq_printf(f,"icache line size: %d\n"
|
||||
"icache ways\t: %d\n"
|
||||
"icache size\t: %d\n"
|
||||
"icache flags\t: "
|
||||
#if XCHAL_ICACHE_LINE_LOCKABLE
|
||||
"lock "
|
||||
#endif
|
||||
"\n"
|
||||
"dcache line size: %d\n"
|
||||
"dcache ways\t: %d\n"
|
||||
"dcache size\t: %d\n"
|
||||
"dcache flags\t: "
|
||||
#if XCHAL_DCACHE_IS_WRITEBACK
|
||||
"writeback "
|
||||
#endif
|
||||
#if XCHAL_DCACHE_LINE_LOCKABLE
|
||||
"lock "
|
||||
#endif
|
||||
"\n",
|
||||
XCHAL_ICACHE_LINESIZE,
|
||||
XCHAL_ICACHE_WAYS,
|
||||
XCHAL_ICACHE_SIZE,
|
||||
XCHAL_DCACHE_LINESIZE,
|
||||
XCHAL_DCACHE_WAYS,
|
||||
XCHAL_DCACHE_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We show only CPU #0 info.
|
||||
*/
|
||||
static void *
|
||||
c_start(struct seq_file *f, loff_t *pos)
|
||||
{
|
||||
return (*pos == 0) ? (void *)1 : NULL;
|
||||
}
|
||||
|
||||
static void *
|
||||
c_next(struct seq_file *f, void *v, loff_t *pos)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
c_stop(struct seq_file *f, void *v)
|
||||
{
|
||||
}
|
||||
|
||||
const struct seq_operations cpuinfo_op =
|
||||
{
|
||||
.start = c_start,
|
||||
.next = c_next,
|
||||
.stop = c_stop,
|
||||
.show = c_show,
|
||||
};
|
||||
|
||||
#endif /* CONFIG_PROC_FS */
|
511
arch/xtensa/kernel/signal.c
Normal file
511
arch/xtensa/kernel/signal.c
Normal file
|
@ -0,0 +1,511 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/signal.c
|
||||
*
|
||||
* Default platform functions.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2005, 2006 Tensilica Inc.
|
||||
* Copyright (C) 1991, 1992 Linus Torvalds
|
||||
* 1997-11-28 Modified for POSIX.1b signals by Richard Henderson
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Joe Taylor <joe@tensilica.com>
|
||||
*/
|
||||
|
||||
#include <linux/signal.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/personality.h>
|
||||
#include <linux/tracehook.h>
|
||||
|
||||
#include <asm/ucontext.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/coprocessor.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
#define DEBUG_SIG 0
|
||||
|
||||
extern struct task_struct *coproc_owners[];
|
||||
|
||||
struct rt_sigframe
|
||||
{
|
||||
struct siginfo info;
|
||||
struct ucontext uc;
|
||||
struct {
|
||||
xtregs_opt_t opt;
|
||||
xtregs_user_t user;
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
xtregs_coprocessor_t cp;
|
||||
#endif
|
||||
} xtregs;
|
||||
unsigned char retcode[6];
|
||||
unsigned int window[4];
|
||||
};
|
||||
|
||||
/*
|
||||
* Flush register windows stored in pt_regs to stack.
|
||||
* Returns 1 for errors.
|
||||
*/
|
||||
|
||||
int
|
||||
flush_window_regs_user(struct pt_regs *regs)
|
||||
{
|
||||
const unsigned long ws = regs->windowstart;
|
||||
const unsigned long wb = regs->windowbase;
|
||||
unsigned long sp = 0;
|
||||
unsigned long wm;
|
||||
int err = 1;
|
||||
int base;
|
||||
|
||||
/* Return if no other frames. */
|
||||
|
||||
if (regs->wmask == 1)
|
||||
return 0;
|
||||
|
||||
/* Rotate windowmask and skip empty frames. */
|
||||
|
||||
wm = (ws >> wb) | (ws << (XCHAL_NUM_AREGS / 4 - wb));
|
||||
base = (XCHAL_NUM_AREGS / 4) - (regs->wmask >> 4);
|
||||
|
||||
/* For call8 or call12 frames, we need the previous stack pointer. */
|
||||
|
||||
if ((regs->wmask & 2) == 0)
|
||||
if (__get_user(sp, (int*)(regs->areg[base * 4 + 1] - 12)))
|
||||
goto errout;
|
||||
|
||||
/* Spill frames to stack. */
|
||||
|
||||
while (base < XCHAL_NUM_AREGS / 4) {
|
||||
|
||||
int m = (wm >> base);
|
||||
int inc = 0;
|
||||
|
||||
/* Save registers a4..a7 (call8) or a4...a11 (call12) */
|
||||
|
||||
if (m & 2) { /* call4 */
|
||||
inc = 1;
|
||||
|
||||
} else if (m & 4) { /* call8 */
|
||||
if (copy_to_user((void*)(sp - 32),
|
||||
®s->areg[(base + 1) * 4], 16))
|
||||
goto errout;
|
||||
inc = 2;
|
||||
|
||||
} else if (m & 8) { /* call12 */
|
||||
if (copy_to_user((void*)(sp - 48),
|
||||
®s->areg[(base + 1) * 4], 32))
|
||||
goto errout;
|
||||
inc = 3;
|
||||
}
|
||||
|
||||
/* Save current frame a0..a3 under next SP */
|
||||
|
||||
sp = regs->areg[((base + inc) * 4 + 1) % XCHAL_NUM_AREGS];
|
||||
if (copy_to_user((void*)(sp - 16), ®s->areg[base * 4], 16))
|
||||
goto errout;
|
||||
|
||||
/* Get current stack pointer for next loop iteration. */
|
||||
|
||||
sp = regs->areg[base * 4 + 1];
|
||||
base += inc;
|
||||
}
|
||||
|
||||
regs->wmask = 1;
|
||||
regs->windowstart = 1 << wb;
|
||||
|
||||
return 0;
|
||||
|
||||
errout:
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: We don't copy double exception 'regs', we have to finish double exc.
|
||||
* first before we return to signal handler! This dbl.exc.handler might cause
|
||||
* another double exception, but I think we are fine as the situation is the
|
||||
* same as if we had returned to the signal handerl and got an interrupt
|
||||
* immediately...
|
||||
*/
|
||||
|
||||
static int
|
||||
setup_sigcontext(struct rt_sigframe __user *frame, struct pt_regs *regs)
|
||||
{
|
||||
struct sigcontext __user *sc = &frame->uc.uc_mcontext;
|
||||
struct thread_info *ti = current_thread_info();
|
||||
int err = 0;
|
||||
|
||||
#define COPY(x) err |= __put_user(regs->x, &sc->sc_##x)
|
||||
COPY(pc);
|
||||
COPY(ps);
|
||||
COPY(lbeg);
|
||||
COPY(lend);
|
||||
COPY(lcount);
|
||||
COPY(sar);
|
||||
#undef COPY
|
||||
|
||||
err |= flush_window_regs_user(regs);
|
||||
err |= __copy_to_user (sc->sc_a, regs->areg, 16 * 4);
|
||||
err |= __put_user(0, &sc->sc_xtregs);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
coprocessor_flush_all(ti);
|
||||
coprocessor_release_all(ti);
|
||||
err |= __copy_to_user(&frame->xtregs.cp, &ti->xtregs_cp,
|
||||
sizeof (frame->xtregs.cp));
|
||||
#endif
|
||||
err |= __copy_to_user(&frame->xtregs.opt, ®s->xtregs_opt,
|
||||
sizeof (xtregs_opt_t));
|
||||
err |= __copy_to_user(&frame->xtregs.user, &ti->xtregs_user,
|
||||
sizeof (xtregs_user_t));
|
||||
|
||||
err |= __put_user(err ? NULL : &frame->xtregs, &sc->sc_xtregs);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int
|
||||
restore_sigcontext(struct pt_regs *regs, struct rt_sigframe __user *frame)
|
||||
{
|
||||
struct sigcontext __user *sc = &frame->uc.uc_mcontext;
|
||||
struct thread_info *ti = current_thread_info();
|
||||
unsigned int err = 0;
|
||||
unsigned long ps;
|
||||
|
||||
#define COPY(x) err |= __get_user(regs->x, &sc->sc_##x)
|
||||
COPY(pc);
|
||||
COPY(lbeg);
|
||||
COPY(lend);
|
||||
COPY(lcount);
|
||||
COPY(sar);
|
||||
#undef COPY
|
||||
|
||||
/* All registers were flushed to stack. Start with a prestine frame. */
|
||||
|
||||
regs->wmask = 1;
|
||||
regs->windowbase = 0;
|
||||
regs->windowstart = 1;
|
||||
|
||||
regs->syscall = -1; /* disable syscall checks */
|
||||
|
||||
/* For PS, restore only PS.CALLINC.
|
||||
* Assume that all other bits are either the same as for the signal
|
||||
* handler, or the user mode value doesn't matter (e.g. PS.OWB).
|
||||
*/
|
||||
err |= __get_user(ps, &sc->sc_ps);
|
||||
regs->ps = (regs->ps & ~PS_CALLINC_MASK) | (ps & PS_CALLINC_MASK);
|
||||
|
||||
/* Additional corruption checks */
|
||||
|
||||
if ((regs->lcount > 0)
|
||||
&& ((regs->lbeg > TASK_SIZE) || (regs->lend > TASK_SIZE)) )
|
||||
err = 1;
|
||||
|
||||
err |= __copy_from_user(regs->areg, sc->sc_a, 16 * 4);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* The signal handler may have used coprocessors in which
|
||||
* case they are still enabled. We disable them to force a
|
||||
* reloading of the original task's CP state by the lazy
|
||||
* context-switching mechanisms of CP exception handling.
|
||||
* Also, we essentially discard any coprocessor state that the
|
||||
* signal handler created. */
|
||||
|
||||
#if XTENSA_HAVE_COPROCESSORS
|
||||
coprocessor_release_all(ti);
|
||||
err |= __copy_from_user(&ti->xtregs_cp, &frame->xtregs.cp,
|
||||
sizeof (frame->xtregs.cp));
|
||||
#endif
|
||||
err |= __copy_from_user(&ti->xtregs_user, &frame->xtregs.user,
|
||||
sizeof (xtregs_user_t));
|
||||
err |= __copy_from_user(®s->xtregs_opt, &frame->xtregs.opt,
|
||||
sizeof (xtregs_opt_t));
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Do a signal return; undo the signal stack.
|
||||
*/
|
||||
|
||||
asmlinkage long xtensa_rt_sigreturn(long a0, long a1, long a2, long a3,
|
||||
long a4, long a5, struct pt_regs *regs)
|
||||
{
|
||||
struct rt_sigframe __user *frame;
|
||||
sigset_t set;
|
||||
int ret;
|
||||
|
||||
/* Always make any pending restarted system calls return -EINTR */
|
||||
current_thread_info()->restart_block.fn = do_no_restart_syscall;
|
||||
|
||||
if (regs->depc > 64)
|
||||
panic("rt_sigreturn in double exception!\n");
|
||||
|
||||
frame = (struct rt_sigframe __user *) regs->areg[1];
|
||||
|
||||
if (!access_ok(VERIFY_READ, frame, sizeof(*frame)))
|
||||
goto badframe;
|
||||
|
||||
if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set)))
|
||||
goto badframe;
|
||||
|
||||
set_current_blocked(&set);
|
||||
|
||||
if (restore_sigcontext(regs, frame))
|
||||
goto badframe;
|
||||
|
||||
ret = regs->areg[2];
|
||||
|
||||
if (restore_altstack(&frame->uc.uc_stack))
|
||||
goto badframe;
|
||||
|
||||
return ret;
|
||||
|
||||
badframe:
|
||||
force_sig(SIGSEGV, current);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Set up a signal frame.
|
||||
*/
|
||||
|
||||
static int
|
||||
gen_return_code(unsigned char *codemem)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* The 12-bit immediate is really split up within the 24-bit MOVI
|
||||
* instruction. As long as the above system call numbers fit within
|
||||
* 8-bits, the following code works fine. See the Xtensa ISA for
|
||||
* details.
|
||||
*/
|
||||
|
||||
#if __NR_rt_sigreturn > 255
|
||||
# error Generating the MOVI instruction below breaks!
|
||||
#endif
|
||||
|
||||
#ifdef __XTENSA_EB__ /* Big Endian version */
|
||||
/* Generate instruction: MOVI a2, __NR_rt_sigreturn */
|
||||
err |= __put_user(0x22, &codemem[0]);
|
||||
err |= __put_user(0x0a, &codemem[1]);
|
||||
err |= __put_user(__NR_rt_sigreturn, &codemem[2]);
|
||||
/* Generate instruction: SYSCALL */
|
||||
err |= __put_user(0x00, &codemem[3]);
|
||||
err |= __put_user(0x05, &codemem[4]);
|
||||
err |= __put_user(0x00, &codemem[5]);
|
||||
|
||||
#elif defined __XTENSA_EL__ /* Little Endian version */
|
||||
/* Generate instruction: MOVI a2, __NR_rt_sigreturn */
|
||||
err |= __put_user(0x22, &codemem[0]);
|
||||
err |= __put_user(0xa0, &codemem[1]);
|
||||
err |= __put_user(__NR_rt_sigreturn, &codemem[2]);
|
||||
/* Generate instruction: SYSCALL */
|
||||
err |= __put_user(0x00, &codemem[3]);
|
||||
err |= __put_user(0x50, &codemem[4]);
|
||||
err |= __put_user(0x00, &codemem[5]);
|
||||
#else
|
||||
# error Must use compiler for Xtensa processors.
|
||||
#endif
|
||||
|
||||
/* Flush generated code out of the data cache */
|
||||
|
||||
if (err == 0) {
|
||||
__invalidate_icache_range((unsigned long)codemem, 6UL);
|
||||
__flush_invalidate_dcache_range((unsigned long)codemem, 6UL);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static int setup_frame(struct ksignal *ksig, sigset_t *set,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
struct rt_sigframe *frame;
|
||||
int err = 0, sig = ksig->sig;
|
||||
int signal;
|
||||
unsigned long sp, ra, tp;
|
||||
|
||||
sp = regs->areg[1];
|
||||
|
||||
if ((ksig->ka.sa.sa_flags & SA_ONSTACK) != 0 && sas_ss_flags(sp) == 0) {
|
||||
sp = current->sas_ss_sp + current->sas_ss_size;
|
||||
}
|
||||
|
||||
frame = (void *)((sp - sizeof(*frame)) & -16ul);
|
||||
|
||||
if (regs->depc > 64)
|
||||
panic ("Double exception sys_sigreturn\n");
|
||||
|
||||
if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
signal = current_thread_info()->exec_domain
|
||||
&& current_thread_info()->exec_domain->signal_invmap
|
||||
&& sig < 32
|
||||
? current_thread_info()->exec_domain->signal_invmap[sig]
|
||||
: sig;
|
||||
|
||||
if (ksig->ka.sa.sa_flags & SA_SIGINFO) {
|
||||
err |= copy_siginfo_to_user(&frame->info, &ksig->info);
|
||||
}
|
||||
|
||||
/* Create the user context. */
|
||||
|
||||
err |= __put_user(0, &frame->uc.uc_flags);
|
||||
err |= __put_user(0, &frame->uc.uc_link);
|
||||
err |= __save_altstack(&frame->uc.uc_stack, regs->areg[1]);
|
||||
err |= setup_sigcontext(frame, regs);
|
||||
err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
|
||||
|
||||
if (ksig->ka.sa.sa_flags & SA_RESTORER) {
|
||||
ra = (unsigned long)ksig->ka.sa.sa_restorer;
|
||||
} else {
|
||||
|
||||
/* Create sys_rt_sigreturn syscall in stack frame */
|
||||
|
||||
err |= gen_return_code(frame->retcode);
|
||||
|
||||
if (err) {
|
||||
return -EFAULT;
|
||||
}
|
||||
ra = (unsigned long) frame->retcode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create signal handler execution context.
|
||||
* Return context not modified until this point.
|
||||
*/
|
||||
|
||||
/* Set up registers for signal handler; preserve the threadptr */
|
||||
tp = regs->threadptr;
|
||||
start_thread(regs, (unsigned long) ksig->ka.sa.sa_handler,
|
||||
(unsigned long) frame);
|
||||
|
||||
/* Set up a stack frame for a call4
|
||||
* Note: PS.CALLINC is set to one by start_thread
|
||||
*/
|
||||
regs->areg[4] = (((unsigned long) ra) & 0x3fffffff) | 0x40000000;
|
||||
regs->areg[6] = (unsigned long) signal;
|
||||
regs->areg[7] = (unsigned long) &frame->info;
|
||||
regs->areg[8] = (unsigned long) &frame->uc;
|
||||
regs->threadptr = tp;
|
||||
|
||||
/* Set access mode to USER_DS. Nomenclature is outdated, but
|
||||
* functionality is used in uaccess.h
|
||||
*/
|
||||
set_fs(USER_DS);
|
||||
|
||||
#if DEBUG_SIG
|
||||
printk("SIG rt deliver (%s:%d): signal=%d sp=%p pc=%08x\n",
|
||||
current->comm, current->pid, signal, frame, regs->pc);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that 'init' is a special process: it doesn't get signals it doesn't
|
||||
* want to handle. Thus you cannot kill init even with a SIGKILL even by
|
||||
* mistake.
|
||||
*
|
||||
* Note that we go through the signals twice: once to check the signals that
|
||||
* the kernel can handle, and then we build all the user-level signal handling
|
||||
* stack-frames in one go after that.
|
||||
*/
|
||||
static void do_signal(struct pt_regs *regs)
|
||||
{
|
||||
struct ksignal ksig;
|
||||
|
||||
task_pt_regs(current)->icountlevel = 0;
|
||||
|
||||
if (get_signal(&ksig)) {
|
||||
int ret;
|
||||
|
||||
/* Are we from a system call? */
|
||||
|
||||
if ((signed)regs->syscall >= 0) {
|
||||
|
||||
/* If so, check system call restarting.. */
|
||||
|
||||
switch (regs->areg[2]) {
|
||||
case -ERESTARTNOHAND:
|
||||
case -ERESTART_RESTARTBLOCK:
|
||||
regs->areg[2] = -EINTR;
|
||||
break;
|
||||
|
||||
case -ERESTARTSYS:
|
||||
if (!(ksig.ka.sa.sa_flags & SA_RESTART)) {
|
||||
regs->areg[2] = -EINTR;
|
||||
break;
|
||||
}
|
||||
/* fallthrough */
|
||||
case -ERESTARTNOINTR:
|
||||
regs->areg[2] = regs->syscall;
|
||||
regs->pc -= 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* nothing to do */
|
||||
if (regs->areg[2] != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Whee! Actually deliver the signal. */
|
||||
/* Set up the stack frame */
|
||||
ret = setup_frame(&ksig, sigmask_to_save(), regs);
|
||||
signal_setup_done(ret, &ksig, 0);
|
||||
if (current->ptrace & PT_SINGLESTEP)
|
||||
task_pt_regs(current)->icountlevel = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Did we come from a system call? */
|
||||
if ((signed) regs->syscall >= 0) {
|
||||
/* Restart the system call - no handlers present */
|
||||
switch (regs->areg[2]) {
|
||||
case -ERESTARTNOHAND:
|
||||
case -ERESTARTSYS:
|
||||
case -ERESTARTNOINTR:
|
||||
regs->areg[2] = regs->syscall;
|
||||
regs->pc -= 3;
|
||||
break;
|
||||
case -ERESTART_RESTARTBLOCK:
|
||||
regs->areg[2] = __NR_restart_syscall;
|
||||
regs->pc -= 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there's no signal to deliver, we just restore the saved mask. */
|
||||
restore_saved_sigmask();
|
||||
|
||||
if (current->ptrace & PT_SINGLESTEP)
|
||||
task_pt_regs(current)->icountlevel = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
void do_notify_resume(struct pt_regs *regs)
|
||||
{
|
||||
if (test_thread_flag(TIF_SIGPENDING))
|
||||
do_signal(regs);
|
||||
|
||||
if (test_and_clear_thread_flag(TIF_NOTIFY_RESUME))
|
||||
tracehook_notify_resume(regs);
|
||||
}
|
608
arch/xtensa/kernel/smp.c
Normal file
608
arch/xtensa/kernel/smp.c
Normal file
|
@ -0,0 +1,608 @@
|
|||
/*
|
||||
* Xtensa SMP support functions.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2008 - 2013 Tensilica Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Joe Taylor <joe@tensilica.com>
|
||||
* Pete Delaney <piet@tensilica.com
|
||||
*/
|
||||
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kdebug.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/thread_info.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/kdebug.h>
|
||||
#include <asm/mmu_context.h>
|
||||
#include <asm/mxregs.h>
|
||||
#include <asm/platform.h>
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/traps.h>
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
# if XCHAL_HAVE_S32C1I == 0
|
||||
# error "The S32C1I option is required for SMP."
|
||||
# endif
|
||||
#endif
|
||||
|
||||
static void system_invalidate_dcache_range(unsigned long start,
|
||||
unsigned long size);
|
||||
static void system_flush_invalidate_dcache_range(unsigned long start,
|
||||
unsigned long size);
|
||||
|
||||
/* IPI (Inter Process Interrupt) */
|
||||
|
||||
#define IPI_IRQ 0
|
||||
|
||||
static irqreturn_t ipi_interrupt(int irq, void *dev_id);
|
||||
static struct irqaction ipi_irqaction = {
|
||||
.handler = ipi_interrupt,
|
||||
.flags = IRQF_PERCPU,
|
||||
.name = "ipi",
|
||||
};
|
||||
|
||||
void ipi_init(void)
|
||||
{
|
||||
unsigned irq = irq_create_mapping(NULL, IPI_IRQ);
|
||||
setup_irq(irq, &ipi_irqaction);
|
||||
}
|
||||
|
||||
static inline unsigned int get_core_count(void)
|
||||
{
|
||||
/* Bits 18..21 of SYSCFGID contain the core count minus 1. */
|
||||
unsigned int syscfgid = get_er(SYSCFGID);
|
||||
return ((syscfgid >> 18) & 0xf) + 1;
|
||||
}
|
||||
|
||||
static inline int get_core_id(void)
|
||||
{
|
||||
/* Bits 0...18 of SYSCFGID contain the core id */
|
||||
unsigned int core_id = get_er(SYSCFGID);
|
||||
return core_id & 0x3fff;
|
||||
}
|
||||
|
||||
void __init smp_prepare_cpus(unsigned int max_cpus)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < max_cpus; ++i)
|
||||
set_cpu_present(i, true);
|
||||
}
|
||||
|
||||
void __init smp_init_cpus(void)
|
||||
{
|
||||
unsigned i;
|
||||
unsigned int ncpus = get_core_count();
|
||||
unsigned int core_id = get_core_id();
|
||||
|
||||
pr_info("%s: Core Count = %d\n", __func__, ncpus);
|
||||
pr_info("%s: Core Id = %d\n", __func__, core_id);
|
||||
|
||||
for (i = 0; i < ncpus; ++i)
|
||||
set_cpu_possible(i, true);
|
||||
}
|
||||
|
||||
void __init smp_prepare_boot_cpu(void)
|
||||
{
|
||||
unsigned int cpu = smp_processor_id();
|
||||
BUG_ON(cpu != 0);
|
||||
cpu_asid_cache(cpu) = ASID_USER_FIRST;
|
||||
}
|
||||
|
||||
void __init smp_cpus_done(unsigned int max_cpus)
|
||||
{
|
||||
}
|
||||
|
||||
static int boot_secondary_processors = 1; /* Set with xt-gdb via .xt-gdb */
|
||||
static DECLARE_COMPLETION(cpu_running);
|
||||
|
||||
void secondary_start_kernel(void)
|
||||
{
|
||||
struct mm_struct *mm = &init_mm;
|
||||
unsigned int cpu = smp_processor_id();
|
||||
|
||||
init_mmu();
|
||||
|
||||
#ifdef CONFIG_DEBUG_KERNEL
|
||||
if (boot_secondary_processors == 0) {
|
||||
pr_debug("%s: boot_secondary_processors:%d; Hanging cpu:%d\n",
|
||||
__func__, boot_secondary_processors, cpu);
|
||||
for (;;)
|
||||
__asm__ __volatile__ ("waiti " __stringify(LOCKLEVEL));
|
||||
}
|
||||
|
||||
pr_debug("%s: boot_secondary_processors:%d; Booting cpu:%d\n",
|
||||
__func__, boot_secondary_processors, cpu);
|
||||
#endif
|
||||
/* Init EXCSAVE1 */
|
||||
|
||||
secondary_trap_init();
|
||||
|
||||
/* All kernel threads share the same mm context. */
|
||||
|
||||
atomic_inc(&mm->mm_users);
|
||||
atomic_inc(&mm->mm_count);
|
||||
current->active_mm = mm;
|
||||
cpumask_set_cpu(cpu, mm_cpumask(mm));
|
||||
enter_lazy_tlb(mm, current);
|
||||
|
||||
preempt_disable();
|
||||
trace_hardirqs_off();
|
||||
|
||||
calibrate_delay();
|
||||
|
||||
notify_cpu_starting(cpu);
|
||||
|
||||
secondary_init_irq();
|
||||
local_timer_setup(cpu);
|
||||
|
||||
set_cpu_online(cpu, true);
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
complete(&cpu_running);
|
||||
|
||||
cpu_startup_entry(CPUHP_ONLINE);
|
||||
}
|
||||
|
||||
static void mx_cpu_start(void *p)
|
||||
{
|
||||
unsigned cpu = (unsigned)p;
|
||||
unsigned long run_stall_mask = get_er(MPSCORE);
|
||||
|
||||
set_er(run_stall_mask & ~(1u << cpu), MPSCORE);
|
||||
pr_debug("%s: cpu: %d, run_stall_mask: %lx ---> %lx\n",
|
||||
__func__, cpu, run_stall_mask, get_er(MPSCORE));
|
||||
}
|
||||
|
||||
static void mx_cpu_stop(void *p)
|
||||
{
|
||||
unsigned cpu = (unsigned)p;
|
||||
unsigned long run_stall_mask = get_er(MPSCORE);
|
||||
|
||||
set_er(run_stall_mask | (1u << cpu), MPSCORE);
|
||||
pr_debug("%s: cpu: %d, run_stall_mask: %lx ---> %lx\n",
|
||||
__func__, cpu, run_stall_mask, get_er(MPSCORE));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
unsigned long cpu_start_id __cacheline_aligned;
|
||||
#endif
|
||||
unsigned long cpu_start_ccount;
|
||||
|
||||
static int boot_secondary(unsigned int cpu, struct task_struct *ts)
|
||||
{
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
|
||||
unsigned long ccount;
|
||||
int i;
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
cpu_start_id = cpu;
|
||||
system_flush_invalidate_dcache_range(
|
||||
(unsigned long)&cpu_start_id, sizeof(cpu_start_id));
|
||||
#endif
|
||||
smp_call_function_single(0, mx_cpu_start, (void *)cpu, 1);
|
||||
|
||||
for (i = 0; i < 2; ++i) {
|
||||
do
|
||||
ccount = get_ccount();
|
||||
while (!ccount);
|
||||
|
||||
cpu_start_ccount = ccount;
|
||||
|
||||
while (time_before(jiffies, timeout)) {
|
||||
mb();
|
||||
if (!cpu_start_ccount)
|
||||
break;
|
||||
}
|
||||
|
||||
if (cpu_start_ccount) {
|
||||
smp_call_function_single(0, mx_cpu_stop,
|
||||
(void *)cpu, 1);
|
||||
cpu_start_ccount = 0;
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __cpu_up(unsigned int cpu, struct task_struct *idle)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (cpu_asid_cache(cpu) == 0)
|
||||
cpu_asid_cache(cpu) = ASID_USER_FIRST;
|
||||
|
||||
start_info.stack = (unsigned long)task_pt_regs(idle);
|
||||
wmb();
|
||||
|
||||
pr_debug("%s: Calling wakeup_secondary(cpu:%d, idle:%p, sp: %08lx)\n",
|
||||
__func__, cpu, idle, start_info.stack);
|
||||
|
||||
ret = boot_secondary(cpu, idle);
|
||||
if (ret == 0) {
|
||||
wait_for_completion_timeout(&cpu_running,
|
||||
msecs_to_jiffies(1000));
|
||||
if (!cpu_online(cpu))
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
pr_err("CPU %u failed to boot\n", cpu);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
|
||||
/*
|
||||
* __cpu_disable runs on the processor to be shutdown.
|
||||
*/
|
||||
int __cpu_disable(void)
|
||||
{
|
||||
unsigned int cpu = smp_processor_id();
|
||||
|
||||
/*
|
||||
* Take this CPU offline. Once we clear this, we can't return,
|
||||
* and we must not schedule until we're ready to give up the cpu.
|
||||
*/
|
||||
set_cpu_online(cpu, false);
|
||||
|
||||
/*
|
||||
* OK - migrate IRQs away from this CPU
|
||||
*/
|
||||
migrate_irqs();
|
||||
|
||||
/*
|
||||
* Flush user cache and TLB mappings, and then remove this CPU
|
||||
* from the vm mask set of all processes.
|
||||
*/
|
||||
local_flush_cache_all();
|
||||
local_flush_tlb_all();
|
||||
invalidate_page_directory();
|
||||
|
||||
clear_tasks_mm_cpumask(cpu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void platform_cpu_kill(unsigned int cpu)
|
||||
{
|
||||
smp_call_function_single(0, mx_cpu_stop, (void *)cpu, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* called on the thread which is asking for a CPU to be shutdown -
|
||||
* waits until shutdown has completed, or it is timed out.
|
||||
*/
|
||||
void __cpu_die(unsigned int cpu)
|
||||
{
|
||||
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
|
||||
while (time_before(jiffies, timeout)) {
|
||||
system_invalidate_dcache_range((unsigned long)&cpu_start_id,
|
||||
sizeof(cpu_start_id));
|
||||
if (cpu_start_id == -cpu) {
|
||||
platform_cpu_kill(cpu);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pr_err("CPU%u: unable to kill\n", cpu);
|
||||
}
|
||||
|
||||
void arch_cpu_idle_dead(void)
|
||||
{
|
||||
cpu_die();
|
||||
}
|
||||
/*
|
||||
* Called from the idle thread for the CPU which has been shutdown.
|
||||
*
|
||||
* Note that we disable IRQs here, but do not re-enable them
|
||||
* before returning to the caller. This is also the behaviour
|
||||
* of the other hotplug-cpu capable cores, so presumably coming
|
||||
* out of idle fixes this.
|
||||
*/
|
||||
void __ref cpu_die(void)
|
||||
{
|
||||
idle_task_exit();
|
||||
local_irq_disable();
|
||||
__asm__ __volatile__(
|
||||
" movi a2, cpu_restart\n"
|
||||
" jx a2\n");
|
||||
}
|
||||
|
||||
#endif /* CONFIG_HOTPLUG_CPU */
|
||||
|
||||
enum ipi_msg_type {
|
||||
IPI_RESCHEDULE = 0,
|
||||
IPI_CALL_FUNC,
|
||||
IPI_CPU_STOP,
|
||||
IPI_MAX
|
||||
};
|
||||
|
||||
static const struct {
|
||||
const char *short_text;
|
||||
const char *long_text;
|
||||
} ipi_text[] = {
|
||||
{ .short_text = "RES", .long_text = "Rescheduling interrupts" },
|
||||
{ .short_text = "CAL", .long_text = "Function call interrupts" },
|
||||
{ .short_text = "DIE", .long_text = "CPU shutdown interrupts" },
|
||||
};
|
||||
|
||||
struct ipi_data {
|
||||
unsigned long ipi_count[IPI_MAX];
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct ipi_data, ipi_data);
|
||||
|
||||
static void send_ipi_message(const struct cpumask *callmask,
|
||||
enum ipi_msg_type msg_id)
|
||||
{
|
||||
int index;
|
||||
unsigned long mask = 0;
|
||||
|
||||
for_each_cpu(index, callmask)
|
||||
if (index != smp_processor_id())
|
||||
mask |= 1 << index;
|
||||
|
||||
set_er(mask, MIPISET(msg_id));
|
||||
}
|
||||
|
||||
void arch_send_call_function_ipi_mask(const struct cpumask *mask)
|
||||
{
|
||||
send_ipi_message(mask, IPI_CALL_FUNC);
|
||||
}
|
||||
|
||||
void arch_send_call_function_single_ipi(int cpu)
|
||||
{
|
||||
send_ipi_message(cpumask_of(cpu), IPI_CALL_FUNC);
|
||||
}
|
||||
|
||||
void smp_send_reschedule(int cpu)
|
||||
{
|
||||
send_ipi_message(cpumask_of(cpu), IPI_RESCHEDULE);
|
||||
}
|
||||
|
||||
void smp_send_stop(void)
|
||||
{
|
||||
struct cpumask targets;
|
||||
|
||||
cpumask_copy(&targets, cpu_online_mask);
|
||||
cpumask_clear_cpu(smp_processor_id(), &targets);
|
||||
send_ipi_message(&targets, IPI_CPU_STOP);
|
||||
}
|
||||
|
||||
static void ipi_cpu_stop(unsigned int cpu)
|
||||
{
|
||||
set_cpu_online(cpu, false);
|
||||
machine_halt();
|
||||
}
|
||||
|
||||
irqreturn_t ipi_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
unsigned int cpu = smp_processor_id();
|
||||
struct ipi_data *ipi = &per_cpu(ipi_data, cpu);
|
||||
unsigned int msg;
|
||||
unsigned i;
|
||||
|
||||
msg = get_er(MIPICAUSE(cpu));
|
||||
for (i = 0; i < IPI_MAX; i++)
|
||||
if (msg & (1 << i)) {
|
||||
set_er(1 << i, MIPICAUSE(cpu));
|
||||
++ipi->ipi_count[i];
|
||||
}
|
||||
|
||||
if (msg & (1 << IPI_RESCHEDULE))
|
||||
scheduler_ipi();
|
||||
if (msg & (1 << IPI_CALL_FUNC))
|
||||
generic_smp_call_function_interrupt();
|
||||
if (msg & (1 << IPI_CPU_STOP))
|
||||
ipi_cpu_stop(cpu);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
void show_ipi_list(struct seq_file *p, int prec)
|
||||
{
|
||||
unsigned int cpu;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < IPI_MAX; ++i) {
|
||||
seq_printf(p, "%*s:", prec, ipi_text[i].short_text);
|
||||
for_each_online_cpu(cpu)
|
||||
seq_printf(p, " %10lu",
|
||||
per_cpu(ipi_data, cpu).ipi_count[i]);
|
||||
seq_printf(p, " %s\n", ipi_text[i].long_text);
|
||||
}
|
||||
}
|
||||
|
||||
int setup_profiling_timer(unsigned int multiplier)
|
||||
{
|
||||
pr_debug("setup_profiling_timer %d\n", multiplier);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TLB flush functions */
|
||||
|
||||
struct flush_data {
|
||||
struct vm_area_struct *vma;
|
||||
unsigned long addr1;
|
||||
unsigned long addr2;
|
||||
};
|
||||
|
||||
static void ipi_flush_tlb_all(void *arg)
|
||||
{
|
||||
local_flush_tlb_all();
|
||||
}
|
||||
|
||||
void flush_tlb_all(void)
|
||||
{
|
||||
on_each_cpu(ipi_flush_tlb_all, NULL, 1);
|
||||
}
|
||||
|
||||
static void ipi_flush_tlb_mm(void *arg)
|
||||
{
|
||||
local_flush_tlb_mm(arg);
|
||||
}
|
||||
|
||||
void flush_tlb_mm(struct mm_struct *mm)
|
||||
{
|
||||
on_each_cpu(ipi_flush_tlb_mm, mm, 1);
|
||||
}
|
||||
|
||||
static void ipi_flush_tlb_page(void *arg)
|
||||
{
|
||||
struct flush_data *fd = arg;
|
||||
local_flush_tlb_page(fd->vma, fd->addr1);
|
||||
}
|
||||
|
||||
void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)
|
||||
{
|
||||
struct flush_data fd = {
|
||||
.vma = vma,
|
||||
.addr1 = addr,
|
||||
};
|
||||
on_each_cpu(ipi_flush_tlb_page, &fd, 1);
|
||||
}
|
||||
|
||||
static void ipi_flush_tlb_range(void *arg)
|
||||
{
|
||||
struct flush_data *fd = arg;
|
||||
local_flush_tlb_range(fd->vma, fd->addr1, fd->addr2);
|
||||
}
|
||||
|
||||
void flush_tlb_range(struct vm_area_struct *vma,
|
||||
unsigned long start, unsigned long end)
|
||||
{
|
||||
struct flush_data fd = {
|
||||
.vma = vma,
|
||||
.addr1 = start,
|
||||
.addr2 = end,
|
||||
};
|
||||
on_each_cpu(ipi_flush_tlb_range, &fd, 1);
|
||||
}
|
||||
|
||||
static void ipi_flush_tlb_kernel_range(void *arg)
|
||||
{
|
||||
struct flush_data *fd = arg;
|
||||
local_flush_tlb_kernel_range(fd->addr1, fd->addr2);
|
||||
}
|
||||
|
||||
void flush_tlb_kernel_range(unsigned long start, unsigned long end)
|
||||
{
|
||||
struct flush_data fd = {
|
||||
.addr1 = start,
|
||||
.addr2 = end,
|
||||
};
|
||||
on_each_cpu(ipi_flush_tlb_kernel_range, &fd, 1);
|
||||
}
|
||||
|
||||
/* Cache flush functions */
|
||||
|
||||
static void ipi_flush_cache_all(void *arg)
|
||||
{
|
||||
local_flush_cache_all();
|
||||
}
|
||||
|
||||
void flush_cache_all(void)
|
||||
{
|
||||
on_each_cpu(ipi_flush_cache_all, NULL, 1);
|
||||
}
|
||||
|
||||
static void ipi_flush_cache_page(void *arg)
|
||||
{
|
||||
struct flush_data *fd = arg;
|
||||
local_flush_cache_page(fd->vma, fd->addr1, fd->addr2);
|
||||
}
|
||||
|
||||
void flush_cache_page(struct vm_area_struct *vma,
|
||||
unsigned long address, unsigned long pfn)
|
||||
{
|
||||
struct flush_data fd = {
|
||||
.vma = vma,
|
||||
.addr1 = address,
|
||||
.addr2 = pfn,
|
||||
};
|
||||
on_each_cpu(ipi_flush_cache_page, &fd, 1);
|
||||
}
|
||||
|
||||
static void ipi_flush_cache_range(void *arg)
|
||||
{
|
||||
struct flush_data *fd = arg;
|
||||
local_flush_cache_range(fd->vma, fd->addr1, fd->addr2);
|
||||
}
|
||||
|
||||
void flush_cache_range(struct vm_area_struct *vma,
|
||||
unsigned long start, unsigned long end)
|
||||
{
|
||||
struct flush_data fd = {
|
||||
.vma = vma,
|
||||
.addr1 = start,
|
||||
.addr2 = end,
|
||||
};
|
||||
on_each_cpu(ipi_flush_cache_range, &fd, 1);
|
||||
}
|
||||
|
||||
static void ipi_flush_icache_range(void *arg)
|
||||
{
|
||||
struct flush_data *fd = arg;
|
||||
local_flush_icache_range(fd->addr1, fd->addr2);
|
||||
}
|
||||
|
||||
void flush_icache_range(unsigned long start, unsigned long end)
|
||||
{
|
||||
struct flush_data fd = {
|
||||
.addr1 = start,
|
||||
.addr2 = end,
|
||||
};
|
||||
on_each_cpu(ipi_flush_icache_range, &fd, 1);
|
||||
}
|
||||
EXPORT_SYMBOL(flush_icache_range);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static void ipi_invalidate_dcache_range(void *arg)
|
||||
{
|
||||
struct flush_data *fd = arg;
|
||||
__invalidate_dcache_range(fd->addr1, fd->addr2);
|
||||
}
|
||||
|
||||
static void system_invalidate_dcache_range(unsigned long start,
|
||||
unsigned long size)
|
||||
{
|
||||
struct flush_data fd = {
|
||||
.addr1 = start,
|
||||
.addr2 = size,
|
||||
};
|
||||
on_each_cpu(ipi_invalidate_dcache_range, &fd, 1);
|
||||
}
|
||||
|
||||
static void ipi_flush_invalidate_dcache_range(void *arg)
|
||||
{
|
||||
struct flush_data *fd = arg;
|
||||
__flush_invalidate_dcache_range(fd->addr1, fd->addr2);
|
||||
}
|
||||
|
||||
static void system_flush_invalidate_dcache_range(unsigned long start,
|
||||
unsigned long size)
|
||||
{
|
||||
struct flush_data fd = {
|
||||
.addr1 = start,
|
||||
.addr2 = size,
|
||||
};
|
||||
on_each_cpu(ipi_flush_invalidate_dcache_range, &fd, 1);
|
||||
}
|
120
arch/xtensa/kernel/stacktrace.c
Normal file
120
arch/xtensa/kernel/stacktrace.c
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/stacktrace.c
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2013 Tensilica Inc.
|
||||
*/
|
||||
#include <linux/export.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/stacktrace.h>
|
||||
|
||||
#include <asm/stacktrace.h>
|
||||
#include <asm/traps.h>
|
||||
|
||||
void walk_stackframe(unsigned long *sp,
|
||||
int (*fn)(struct stackframe *frame, void *data),
|
||||
void *data)
|
||||
{
|
||||
unsigned long a0, a1;
|
||||
unsigned long sp_end;
|
||||
|
||||
a1 = (unsigned long)sp;
|
||||
sp_end = ALIGN(a1, THREAD_SIZE);
|
||||
|
||||
spill_registers();
|
||||
|
||||
while (a1 < sp_end) {
|
||||
struct stackframe frame;
|
||||
|
||||
sp = (unsigned long *)a1;
|
||||
|
||||
a0 = *(sp - 4);
|
||||
a1 = *(sp - 3);
|
||||
|
||||
if (a1 <= (unsigned long)sp)
|
||||
break;
|
||||
|
||||
frame.pc = MAKE_PC_FROM_RA(a0, a1);
|
||||
frame.sp = a1;
|
||||
|
||||
if (fn(&frame, data))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_STACKTRACE
|
||||
|
||||
struct stack_trace_data {
|
||||
struct stack_trace *trace;
|
||||
unsigned skip;
|
||||
};
|
||||
|
||||
static int stack_trace_cb(struct stackframe *frame, void *data)
|
||||
{
|
||||
struct stack_trace_data *trace_data = data;
|
||||
struct stack_trace *trace = trace_data->trace;
|
||||
|
||||
if (trace_data->skip) {
|
||||
--trace_data->skip;
|
||||
return 0;
|
||||
}
|
||||
if (!kernel_text_address(frame->pc))
|
||||
return 0;
|
||||
|
||||
trace->entries[trace->nr_entries++] = frame->pc;
|
||||
return trace->nr_entries >= trace->max_entries;
|
||||
}
|
||||
|
||||
void save_stack_trace_tsk(struct task_struct *task, struct stack_trace *trace)
|
||||
{
|
||||
struct stack_trace_data trace_data = {
|
||||
.trace = trace,
|
||||
.skip = trace->skip,
|
||||
};
|
||||
walk_stackframe(stack_pointer(task), stack_trace_cb, &trace_data);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(save_stack_trace_tsk);
|
||||
|
||||
void save_stack_trace(struct stack_trace *trace)
|
||||
{
|
||||
save_stack_trace_tsk(current, trace);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(save_stack_trace);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FRAME_POINTER
|
||||
|
||||
struct return_addr_data {
|
||||
unsigned long addr;
|
||||
unsigned skip;
|
||||
};
|
||||
|
||||
static int return_address_cb(struct stackframe *frame, void *data)
|
||||
{
|
||||
struct return_addr_data *r = data;
|
||||
|
||||
if (r->skip) {
|
||||
--r->skip;
|
||||
return 0;
|
||||
}
|
||||
if (!kernel_text_address(frame->pc))
|
||||
return 0;
|
||||
r->addr = frame->pc;
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned long return_address(unsigned level)
|
||||
{
|
||||
struct return_addr_data r = {
|
||||
.skip = level + 1,
|
||||
};
|
||||
walk_stackframe(stack_pointer(NULL), return_address_cb, &r);
|
||||
return r.addr;
|
||||
}
|
||||
EXPORT_SYMBOL(return_address);
|
||||
|
||||
#endif
|
95
arch/xtensa/kernel/syscall.c
Normal file
95
arch/xtensa/kernel/syscall.c
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/syscall.c
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2005 Tensilica Inc.
|
||||
* Copyright (C) 2000 Silicon Graphics, Inc.
|
||||
* Copyright (C) 1995 - 2000 by Ralf Baechle
|
||||
*
|
||||
* Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
* Marc Gauthier <marc@tensilica.com, marc@alumni.uwaterloo.ca>
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Kevin Chea
|
||||
*
|
||||
*/
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/syscall.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <linux/linkage.h>
|
||||
#include <linux/stringify.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mman.h>
|
||||
#include <linux/shm.h>
|
||||
|
||||
typedef void (*syscall_t)(void);
|
||||
|
||||
syscall_t sys_call_table[__NR_syscall_count] /* FIXME __cacheline_aligned */= {
|
||||
[0 ... __NR_syscall_count - 1] = (syscall_t)&sys_ni_syscall,
|
||||
|
||||
#define __SYSCALL(nr,symbol,nargs) [ nr ] = (syscall_t)symbol,
|
||||
#include <uapi/asm/unistd.h>
|
||||
};
|
||||
|
||||
#define COLOUR_ALIGN(addr, pgoff) \
|
||||
((((addr) + SHMLBA - 1) & ~(SHMLBA - 1)) + \
|
||||
(((pgoff) << PAGE_SHIFT) & (SHMLBA - 1)))
|
||||
|
||||
asmlinkage long xtensa_shmat(int shmid, char __user *shmaddr, int shmflg)
|
||||
{
|
||||
unsigned long ret;
|
||||
long err;
|
||||
|
||||
err = do_shmat(shmid, shmaddr, shmflg, &ret, SHMLBA);
|
||||
if (err)
|
||||
return err;
|
||||
return (long)ret;
|
||||
}
|
||||
|
||||
asmlinkage long xtensa_fadvise64_64(int fd, int advice,
|
||||
unsigned long long offset, unsigned long long len)
|
||||
{
|
||||
return sys_fadvise64_64(fd, offset, len, advice);
|
||||
}
|
||||
|
||||
unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,
|
||||
unsigned long len, unsigned long pgoff, unsigned long flags)
|
||||
{
|
||||
struct vm_area_struct *vmm;
|
||||
|
||||
if (flags & MAP_FIXED) {
|
||||
/* We do not accept a shared mapping if it would violate
|
||||
* cache aliasing constraints.
|
||||
*/
|
||||
if ((flags & MAP_SHARED) &&
|
||||
((addr - (pgoff << PAGE_SHIFT)) & (SHMLBA - 1)))
|
||||
return -EINVAL;
|
||||
return addr;
|
||||
}
|
||||
|
||||
if (len > TASK_SIZE)
|
||||
return -ENOMEM;
|
||||
if (!addr)
|
||||
addr = TASK_UNMAPPED_BASE;
|
||||
|
||||
if (flags & MAP_SHARED)
|
||||
addr = COLOUR_ALIGN(addr, pgoff);
|
||||
else
|
||||
addr = PAGE_ALIGN(addr);
|
||||
|
||||
for (vmm = find_vma(current->mm, addr); ; vmm = vmm->vm_next) {
|
||||
/* At this point: (!vmm || addr < vmm->vm_end). */
|
||||
if (TASK_SIZE - len < addr)
|
||||
return -ENOMEM;
|
||||
if (!vmm || addr + len <= vmm->vm_start)
|
||||
return addr;
|
||||
addr = vmm->vm_end;
|
||||
if (flags & MAP_SHARED)
|
||||
addr = COLOUR_ALIGN(addr, pgoff);
|
||||
}
|
||||
}
|
181
arch/xtensa/kernel/time.c
Normal file
181
arch/xtensa/kernel/time.c
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/time.c
|
||||
*
|
||||
* Timer and clock support.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2005 Tensilica Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/profile.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/sched_clock.h>
|
||||
|
||||
#include <asm/timex.h>
|
||||
#include <asm/platform.h>
|
||||
|
||||
unsigned long ccount_freq; /* ccount Hz */
|
||||
EXPORT_SYMBOL(ccount_freq);
|
||||
|
||||
static cycle_t ccount_read(struct clocksource *cs)
|
||||
{
|
||||
return (cycle_t)get_ccount();
|
||||
}
|
||||
|
||||
static u64 notrace ccount_sched_clock_read(void)
|
||||
{
|
||||
return get_ccount();
|
||||
}
|
||||
|
||||
static struct clocksource ccount_clocksource = {
|
||||
.name = "ccount",
|
||||
.rating = 200,
|
||||
.read = ccount_read,
|
||||
.mask = CLOCKSOURCE_MASK(32),
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
||||
};
|
||||
|
||||
static int ccount_timer_set_next_event(unsigned long delta,
|
||||
struct clock_event_device *dev);
|
||||
static void ccount_timer_set_mode(enum clock_event_mode mode,
|
||||
struct clock_event_device *evt);
|
||||
struct ccount_timer {
|
||||
struct clock_event_device evt;
|
||||
int irq_enabled;
|
||||
char name[24];
|
||||
};
|
||||
static DEFINE_PER_CPU(struct ccount_timer, ccount_timer);
|
||||
|
||||
static int ccount_timer_set_next_event(unsigned long delta,
|
||||
struct clock_event_device *dev)
|
||||
{
|
||||
unsigned long flags, next;
|
||||
int ret = 0;
|
||||
|
||||
local_irq_save(flags);
|
||||
next = get_ccount() + delta;
|
||||
set_linux_timer(next);
|
||||
if (next - get_ccount() > delta)
|
||||
ret = -ETIME;
|
||||
local_irq_restore(flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ccount_timer_set_mode(enum clock_event_mode mode,
|
||||
struct clock_event_device *evt)
|
||||
{
|
||||
struct ccount_timer *timer =
|
||||
container_of(evt, struct ccount_timer, evt);
|
||||
|
||||
/*
|
||||
* There is no way to disable the timer interrupt at the device level,
|
||||
* only at the intenable register itself. Since enable_irq/disable_irq
|
||||
* calls are nested, we need to make sure that these calls are
|
||||
* balanced.
|
||||
*/
|
||||
switch (mode) {
|
||||
case CLOCK_EVT_MODE_SHUTDOWN:
|
||||
case CLOCK_EVT_MODE_UNUSED:
|
||||
if (timer->irq_enabled) {
|
||||
disable_irq(evt->irq);
|
||||
timer->irq_enabled = 0;
|
||||
}
|
||||
break;
|
||||
case CLOCK_EVT_MODE_RESUME:
|
||||
case CLOCK_EVT_MODE_ONESHOT:
|
||||
if (!timer->irq_enabled) {
|
||||
enable_irq(evt->irq);
|
||||
timer->irq_enabled = 1;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t timer_interrupt(int irq, void *dev_id);
|
||||
static struct irqaction timer_irqaction = {
|
||||
.handler = timer_interrupt,
|
||||
.flags = IRQF_TIMER,
|
||||
.name = "timer",
|
||||
};
|
||||
|
||||
void local_timer_setup(unsigned cpu)
|
||||
{
|
||||
struct ccount_timer *timer = &per_cpu(ccount_timer, cpu);
|
||||
struct clock_event_device *clockevent = &timer->evt;
|
||||
|
||||
timer->irq_enabled = 1;
|
||||
clockevent->name = timer->name;
|
||||
snprintf(timer->name, sizeof(timer->name), "ccount_clockevent_%u", cpu);
|
||||
clockevent->features = CLOCK_EVT_FEAT_ONESHOT;
|
||||
clockevent->rating = 300;
|
||||
clockevent->set_next_event = ccount_timer_set_next_event;
|
||||
clockevent->set_mode = ccount_timer_set_mode;
|
||||
clockevent->cpumask = cpumask_of(cpu);
|
||||
clockevent->irq = irq_create_mapping(NULL, LINUX_TIMER_INT);
|
||||
if (WARN(!clockevent->irq, "error: can't map timer irq"))
|
||||
return;
|
||||
clockevents_config_and_register(clockevent, ccount_freq,
|
||||
0xf, 0xffffffff);
|
||||
}
|
||||
|
||||
void __init time_init(void)
|
||||
{
|
||||
#ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT
|
||||
printk("Calibrating CPU frequency ");
|
||||
platform_calibrate_ccount();
|
||||
printk("%d.%02d MHz\n", (int)ccount_freq/1000000,
|
||||
(int)(ccount_freq/10000)%100);
|
||||
#else
|
||||
ccount_freq = CONFIG_XTENSA_CPU_CLOCK*1000000UL;
|
||||
#endif
|
||||
clocksource_register_hz(&ccount_clocksource, ccount_freq);
|
||||
local_timer_setup(0);
|
||||
setup_irq(this_cpu_ptr(&ccount_timer)->evt.irq, &timer_irqaction);
|
||||
sched_clock_register(ccount_sched_clock_read, 32, ccount_freq);
|
||||
clocksource_of_init();
|
||||
}
|
||||
|
||||
/*
|
||||
* The timer interrupt is called HZ times per second.
|
||||
*/
|
||||
|
||||
irqreturn_t timer_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct clock_event_device *evt = &this_cpu_ptr(&ccount_timer)->evt;
|
||||
|
||||
set_linux_timer(get_linux_timer());
|
||||
evt->event_handler(evt);
|
||||
|
||||
/* Allow platform to do something useful (Wdog). */
|
||||
platform_heartbeat();
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_GENERIC_CALIBRATE_DELAY
|
||||
void calibrate_delay(void)
|
||||
{
|
||||
loops_per_jiffy = ccount_freq / HZ;
|
||||
printk("Calibrating delay loop (skipped)... "
|
||||
"%lu.%02lu BogoMIPS preset\n",
|
||||
loops_per_jiffy/(1000000/HZ),
|
||||
(loops_per_jiffy/(10000/HZ)) % 100);
|
||||
}
|
||||
#endif
|
517
arch/xtensa/kernel/traps.c
Normal file
517
arch/xtensa/kernel/traps.c
Normal file
|
@ -0,0 +1,517 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/traps.c
|
||||
*
|
||||
* Exception handling.
|
||||
*
|
||||
* Derived from code with the following copyrights:
|
||||
* Copyright (C) 1994 - 1999 by Ralf Baechle
|
||||
* Modified for R3000 by Paul M. Antoine, 1995, 1996
|
||||
* Complete output from die() by Ulf Carlsson, 1998
|
||||
* Copyright (C) 1999 Silicon Graphics, Inc.
|
||||
*
|
||||
* Essentially rewritten for the Xtensa architecture port.
|
||||
*
|
||||
* Copyright (C) 2001 - 2013 Tensilica Inc.
|
||||
*
|
||||
* Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Marc Gauthier<marc@tensilica.com, marc@alumni.uwaterloo.ca>
|
||||
* Kevin Chea
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/stringify.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hardirq.h>
|
||||
|
||||
#include <asm/stacktrace.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/timex.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/traps.h>
|
||||
|
||||
#ifdef CONFIG_KGDB
|
||||
extern int gdb_enter;
|
||||
extern int return_from_debug_flag;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Machine specific interrupt handlers
|
||||
*/
|
||||
|
||||
extern void kernel_exception(void);
|
||||
extern void user_exception(void);
|
||||
|
||||
extern void fast_syscall_kernel(void);
|
||||
extern void fast_syscall_user(void);
|
||||
extern void fast_alloca(void);
|
||||
extern void fast_unaligned(void);
|
||||
extern void fast_second_level_miss(void);
|
||||
extern void fast_store_prohibited(void);
|
||||
extern void fast_coprocessor(void);
|
||||
|
||||
extern void do_illegal_instruction (struct pt_regs*);
|
||||
extern void do_interrupt (struct pt_regs*);
|
||||
extern void do_unaligned_user (struct pt_regs*);
|
||||
extern void do_multihit (struct pt_regs*, unsigned long);
|
||||
extern void do_page_fault (struct pt_regs*, unsigned long);
|
||||
extern void do_debug (struct pt_regs*);
|
||||
extern void system_call (struct pt_regs*);
|
||||
|
||||
/*
|
||||
* The vector table must be preceded by a save area (which
|
||||
* implies it must be in RAM, unless one places RAM immediately
|
||||
* before a ROM and puts the vector at the start of the ROM (!))
|
||||
*/
|
||||
|
||||
#define KRNL 0x01
|
||||
#define USER 0x02
|
||||
|
||||
#define COPROCESSOR(x) \
|
||||
{ EXCCAUSE_COPROCESSOR ## x ## _DISABLED, USER, fast_coprocessor }
|
||||
|
||||
typedef struct {
|
||||
int cause;
|
||||
int fast;
|
||||
void* handler;
|
||||
} dispatch_init_table_t;
|
||||
|
||||
static dispatch_init_table_t __initdata dispatch_init_table[] = {
|
||||
|
||||
{ EXCCAUSE_ILLEGAL_INSTRUCTION, 0, do_illegal_instruction},
|
||||
{ EXCCAUSE_SYSTEM_CALL, KRNL, fast_syscall_kernel },
|
||||
{ EXCCAUSE_SYSTEM_CALL, USER, fast_syscall_user },
|
||||
{ EXCCAUSE_SYSTEM_CALL, 0, system_call },
|
||||
/* EXCCAUSE_INSTRUCTION_FETCH unhandled */
|
||||
/* EXCCAUSE_LOAD_STORE_ERROR unhandled*/
|
||||
{ EXCCAUSE_LEVEL1_INTERRUPT, 0, do_interrupt },
|
||||
{ EXCCAUSE_ALLOCA, USER|KRNL, fast_alloca },
|
||||
/* EXCCAUSE_INTEGER_DIVIDE_BY_ZERO unhandled */
|
||||
/* EXCCAUSE_PRIVILEGED unhandled */
|
||||
#if XCHAL_UNALIGNED_LOAD_EXCEPTION || XCHAL_UNALIGNED_STORE_EXCEPTION
|
||||
#ifdef CONFIG_XTENSA_UNALIGNED_USER
|
||||
{ EXCCAUSE_UNALIGNED, USER, fast_unaligned },
|
||||
#endif
|
||||
{ EXCCAUSE_UNALIGNED, 0, do_unaligned_user },
|
||||
{ EXCCAUSE_UNALIGNED, KRNL, fast_unaligned },
|
||||
#endif
|
||||
#ifdef CONFIG_MMU
|
||||
{ EXCCAUSE_ITLB_MISS, 0, do_page_fault },
|
||||
{ EXCCAUSE_ITLB_MISS, USER|KRNL, fast_second_level_miss},
|
||||
{ EXCCAUSE_ITLB_MULTIHIT, 0, do_multihit },
|
||||
{ EXCCAUSE_ITLB_PRIVILEGE, 0, do_page_fault },
|
||||
/* EXCCAUSE_SIZE_RESTRICTION unhandled */
|
||||
{ EXCCAUSE_FETCH_CACHE_ATTRIBUTE, 0, do_page_fault },
|
||||
{ EXCCAUSE_DTLB_MISS, USER|KRNL, fast_second_level_miss},
|
||||
{ EXCCAUSE_DTLB_MISS, 0, do_page_fault },
|
||||
{ EXCCAUSE_DTLB_MULTIHIT, 0, do_multihit },
|
||||
{ EXCCAUSE_DTLB_PRIVILEGE, 0, do_page_fault },
|
||||
/* EXCCAUSE_DTLB_SIZE_RESTRICTION unhandled */
|
||||
{ EXCCAUSE_STORE_CACHE_ATTRIBUTE, USER|KRNL, fast_store_prohibited },
|
||||
{ EXCCAUSE_STORE_CACHE_ATTRIBUTE, 0, do_page_fault },
|
||||
{ EXCCAUSE_LOAD_CACHE_ATTRIBUTE, 0, do_page_fault },
|
||||
#endif /* CONFIG_MMU */
|
||||
/* XCCHAL_EXCCAUSE_FLOATING_POINT unhandled */
|
||||
#if XTENSA_HAVE_COPROCESSOR(0)
|
||||
COPROCESSOR(0),
|
||||
#endif
|
||||
#if XTENSA_HAVE_COPROCESSOR(1)
|
||||
COPROCESSOR(1),
|
||||
#endif
|
||||
#if XTENSA_HAVE_COPROCESSOR(2)
|
||||
COPROCESSOR(2),
|
||||
#endif
|
||||
#if XTENSA_HAVE_COPROCESSOR(3)
|
||||
COPROCESSOR(3),
|
||||
#endif
|
||||
#if XTENSA_HAVE_COPROCESSOR(4)
|
||||
COPROCESSOR(4),
|
||||
#endif
|
||||
#if XTENSA_HAVE_COPROCESSOR(5)
|
||||
COPROCESSOR(5),
|
||||
#endif
|
||||
#if XTENSA_HAVE_COPROCESSOR(6)
|
||||
COPROCESSOR(6),
|
||||
#endif
|
||||
#if XTENSA_HAVE_COPROCESSOR(7)
|
||||
COPROCESSOR(7),
|
||||
#endif
|
||||
{ EXCCAUSE_MAPPED_DEBUG, 0, do_debug },
|
||||
{ -1, -1, 0 }
|
||||
|
||||
};
|
||||
|
||||
/* The exception table <exc_table> serves two functions:
|
||||
* 1. it contains three dispatch tables (fast_user, fast_kernel, default-c)
|
||||
* 2. it is a temporary memory buffer for the exception handlers.
|
||||
*/
|
||||
|
||||
DEFINE_PER_CPU(unsigned long, exc_table[EXC_TABLE_SIZE/4]);
|
||||
|
||||
void die(const char*, struct pt_regs*, long);
|
||||
|
||||
static inline void
|
||||
__die_if_kernel(const char *str, struct pt_regs *regs, long err)
|
||||
{
|
||||
if (!user_mode(regs))
|
||||
die(str, regs, err);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unhandled Exceptions. Kill user task or panic if in kernel space.
|
||||
*/
|
||||
|
||||
void do_unhandled(struct pt_regs *regs, unsigned long exccause)
|
||||
{
|
||||
__die_if_kernel("Caught unhandled exception - should not happen",
|
||||
regs, SIGKILL);
|
||||
|
||||
/* If in user mode, send SIGILL signal to current process */
|
||||
printk("Caught unhandled exception in '%s' "
|
||||
"(pid = %d, pc = %#010lx) - should not happen\n"
|
||||
"\tEXCCAUSE is %ld\n",
|
||||
current->comm, task_pid_nr(current), regs->pc, exccause);
|
||||
force_sig(SIGILL, current);
|
||||
}
|
||||
|
||||
/*
|
||||
* Multi-hit exception. This if fatal!
|
||||
*/
|
||||
|
||||
void do_multihit(struct pt_regs *regs, unsigned long exccause)
|
||||
{
|
||||
die("Caught multihit exception", regs, SIGKILL);
|
||||
}
|
||||
|
||||
/*
|
||||
* IRQ handler.
|
||||
*/
|
||||
|
||||
extern void do_IRQ(int, struct pt_regs *);
|
||||
|
||||
void do_interrupt(struct pt_regs *regs)
|
||||
{
|
||||
static const unsigned int_level_mask[] = {
|
||||
0,
|
||||
XCHAL_INTLEVEL1_MASK,
|
||||
XCHAL_INTLEVEL2_MASK,
|
||||
XCHAL_INTLEVEL3_MASK,
|
||||
XCHAL_INTLEVEL4_MASK,
|
||||
XCHAL_INTLEVEL5_MASK,
|
||||
XCHAL_INTLEVEL6_MASK,
|
||||
XCHAL_INTLEVEL7_MASK,
|
||||
};
|
||||
struct pt_regs *old_regs = set_irq_regs(regs);
|
||||
|
||||
irq_enter();
|
||||
|
||||
for (;;) {
|
||||
unsigned intread = get_sr(interrupt);
|
||||
unsigned intenable = get_sr(intenable);
|
||||
unsigned int_at_level = intread & intenable;
|
||||
unsigned level;
|
||||
|
||||
for (level = LOCKLEVEL; level > 0; --level) {
|
||||
if (int_at_level & int_level_mask[level]) {
|
||||
int_at_level &= int_level_mask[level];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (level == 0)
|
||||
break;
|
||||
|
||||
do_IRQ(__ffs(int_at_level), regs);
|
||||
}
|
||||
|
||||
irq_exit();
|
||||
set_irq_regs(old_regs);
|
||||
}
|
||||
|
||||
/*
|
||||
* Illegal instruction. Fatal if in kernel space.
|
||||
*/
|
||||
|
||||
void
|
||||
do_illegal_instruction(struct pt_regs *regs)
|
||||
{
|
||||
__die_if_kernel("Illegal instruction in kernel", regs, SIGKILL);
|
||||
|
||||
/* If in user mode, send SIGILL signal to current process. */
|
||||
|
||||
printk("Illegal Instruction in '%s' (pid = %d, pc = %#010lx)\n",
|
||||
current->comm, task_pid_nr(current), regs->pc);
|
||||
force_sig(SIGILL, current);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handle unaligned memory accesses from user space. Kill task.
|
||||
*
|
||||
* If CONFIG_UNALIGNED_USER is not set, we don't allow unaligned memory
|
||||
* accesses causes from user space.
|
||||
*/
|
||||
|
||||
#if XCHAL_UNALIGNED_LOAD_EXCEPTION || XCHAL_UNALIGNED_STORE_EXCEPTION
|
||||
void
|
||||
do_unaligned_user (struct pt_regs *regs)
|
||||
{
|
||||
siginfo_t info;
|
||||
|
||||
__die_if_kernel("Unhandled unaligned exception in kernel",
|
||||
regs, SIGKILL);
|
||||
|
||||
current->thread.bad_vaddr = regs->excvaddr;
|
||||
current->thread.error_code = -3;
|
||||
printk("Unaligned memory access to %08lx in '%s' "
|
||||
"(pid = %d, pc = %#010lx)\n",
|
||||
regs->excvaddr, current->comm, task_pid_nr(current), regs->pc);
|
||||
info.si_signo = SIGBUS;
|
||||
info.si_errno = 0;
|
||||
info.si_code = BUS_ADRALN;
|
||||
info.si_addr = (void *) regs->excvaddr;
|
||||
force_sig_info(SIGSEGV, &info, current);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
do_debug(struct pt_regs *regs)
|
||||
{
|
||||
#ifdef CONFIG_KGDB
|
||||
/* If remote debugging is configured AND enabled, we give control to
|
||||
* kgdb. Otherwise, we fall through, perhaps giving control to the
|
||||
* native debugger.
|
||||
*/
|
||||
|
||||
if (gdb_enter) {
|
||||
extern void gdb_handle_exception(struct pt_regs *);
|
||||
gdb_handle_exception(regs);
|
||||
return_from_debug_flag = 1;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
__die_if_kernel("Breakpoint in kernel", regs, SIGKILL);
|
||||
|
||||
/* If in user mode, send SIGTRAP signal to current process */
|
||||
|
||||
force_sig(SIGTRAP, current);
|
||||
}
|
||||
|
||||
|
||||
static void set_handler(int idx, void *handler)
|
||||
{
|
||||
unsigned int cpu;
|
||||
|
||||
for_each_possible_cpu(cpu)
|
||||
per_cpu(exc_table, cpu)[idx] = (unsigned long)handler;
|
||||
}
|
||||
|
||||
/* Set exception C handler - for temporary use when probing exceptions */
|
||||
|
||||
void * __init trap_set_handler(int cause, void *handler)
|
||||
{
|
||||
void *previous = (void *)per_cpu(exc_table, 0)[
|
||||
EXC_TABLE_DEFAULT / 4 + cause];
|
||||
set_handler(EXC_TABLE_DEFAULT / 4 + cause, handler);
|
||||
return previous;
|
||||
}
|
||||
|
||||
|
||||
static void trap_init_excsave(void)
|
||||
{
|
||||
unsigned long excsave1 = (unsigned long)this_cpu_ptr(exc_table);
|
||||
__asm__ __volatile__("wsr %0, excsave1\n" : : "a" (excsave1));
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize dispatch tables.
|
||||
*
|
||||
* The exception vectors are stored compressed the __init section in the
|
||||
* dispatch_init_table. This function initializes the following three tables
|
||||
* from that compressed table:
|
||||
* - fast user first dispatch table for user exceptions
|
||||
* - fast kernel first dispatch table for kernel exceptions
|
||||
* - default C-handler C-handler called by the default fast handler.
|
||||
*
|
||||
* See vectors.S for more details.
|
||||
*/
|
||||
|
||||
void __init trap_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Setup default vectors. */
|
||||
|
||||
for(i = 0; i < 64; i++) {
|
||||
set_handler(EXC_TABLE_FAST_USER/4 + i, user_exception);
|
||||
set_handler(EXC_TABLE_FAST_KERNEL/4 + i, kernel_exception);
|
||||
set_handler(EXC_TABLE_DEFAULT/4 + i, do_unhandled);
|
||||
}
|
||||
|
||||
/* Setup specific handlers. */
|
||||
|
||||
for(i = 0; dispatch_init_table[i].cause >= 0; i++) {
|
||||
|
||||
int fast = dispatch_init_table[i].fast;
|
||||
int cause = dispatch_init_table[i].cause;
|
||||
void *handler = dispatch_init_table[i].handler;
|
||||
|
||||
if (fast == 0)
|
||||
set_handler (EXC_TABLE_DEFAULT/4 + cause, handler);
|
||||
if (fast && fast & USER)
|
||||
set_handler (EXC_TABLE_FAST_USER/4 + cause, handler);
|
||||
if (fast && fast & KRNL)
|
||||
set_handler (EXC_TABLE_FAST_KERNEL/4 + cause, handler);
|
||||
}
|
||||
|
||||
/* Initialize EXCSAVE_1 to hold the address of the exception table. */
|
||||
trap_init_excsave();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
void secondary_trap_init(void)
|
||||
{
|
||||
trap_init_excsave();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This function dumps the current valid window frame and other base registers.
|
||||
*/
|
||||
|
||||
void show_regs(struct pt_regs * regs)
|
||||
{
|
||||
int i, wmask;
|
||||
|
||||
show_regs_print_info(KERN_DEFAULT);
|
||||
|
||||
wmask = regs->wmask & ~1;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
if ((i % 8) == 0)
|
||||
printk(KERN_INFO "a%02d:", i);
|
||||
printk(KERN_CONT " %08lx", regs->areg[i]);
|
||||
}
|
||||
printk(KERN_CONT "\n");
|
||||
|
||||
printk("pc: %08lx, ps: %08lx, depc: %08lx, excvaddr: %08lx\n",
|
||||
regs->pc, regs->ps, regs->depc, regs->excvaddr);
|
||||
printk("lbeg: %08lx, lend: %08lx lcount: %08lx, sar: %08lx\n",
|
||||
regs->lbeg, regs->lend, regs->lcount, regs->sar);
|
||||
if (user_mode(regs))
|
||||
printk("wb: %08lx, ws: %08lx, wmask: %08lx, syscall: %ld\n",
|
||||
regs->windowbase, regs->windowstart, regs->wmask,
|
||||
regs->syscall);
|
||||
}
|
||||
|
||||
static int show_trace_cb(struct stackframe *frame, void *data)
|
||||
{
|
||||
if (kernel_text_address(frame->pc)) {
|
||||
printk(" [<%08lx>] ", frame->pc);
|
||||
print_symbol("%s\n", frame->pc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void show_trace(struct task_struct *task, unsigned long *sp)
|
||||
{
|
||||
if (!sp)
|
||||
sp = stack_pointer(task);
|
||||
|
||||
printk("Call Trace:");
|
||||
#ifdef CONFIG_KALLSYMS
|
||||
printk("\n");
|
||||
#endif
|
||||
walk_stackframe(sp, show_trace_cb, NULL);
|
||||
printk("\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* This routine abuses get_user()/put_user() to reference pointers
|
||||
* with at least a bit of error checking ...
|
||||
*/
|
||||
|
||||
static int kstack_depth_to_print = 24;
|
||||
|
||||
void show_stack(struct task_struct *task, unsigned long *sp)
|
||||
{
|
||||
int i = 0;
|
||||
unsigned long *stack;
|
||||
|
||||
if (!sp)
|
||||
sp = stack_pointer(task);
|
||||
stack = sp;
|
||||
|
||||
printk("\nStack: ");
|
||||
|
||||
for (i = 0; i < kstack_depth_to_print; i++) {
|
||||
if (kstack_end(sp))
|
||||
break;
|
||||
if (i && ((i % 8) == 0))
|
||||
printk("\n ");
|
||||
printk("%08lx ", *sp++);
|
||||
}
|
||||
printk("\n");
|
||||
show_trace(task, stack);
|
||||
}
|
||||
|
||||
void show_code(unsigned int *pc)
|
||||
{
|
||||
long i;
|
||||
|
||||
printk("\nCode:");
|
||||
|
||||
for(i = -3 ; i < 6 ; i++) {
|
||||
unsigned long insn;
|
||||
if (__get_user(insn, pc + i)) {
|
||||
printk(" (Bad address in pc)\n");
|
||||
break;
|
||||
}
|
||||
printk("%c%08lx%c",(i?' ':'<'),insn,(i?' ':'>'));
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_SPINLOCK(die_lock);
|
||||
|
||||
void die(const char * str, struct pt_regs * regs, long err)
|
||||
{
|
||||
static int die_counter;
|
||||
int nl = 0;
|
||||
|
||||
console_verbose();
|
||||
spin_lock_irq(&die_lock);
|
||||
|
||||
printk("%s: sig: %ld [#%d]\n", str, err, ++die_counter);
|
||||
#ifdef CONFIG_PREEMPT
|
||||
printk("PREEMPT ");
|
||||
nl = 1;
|
||||
#endif
|
||||
if (nl)
|
||||
printk("\n");
|
||||
show_regs(regs);
|
||||
if (!user_mode(regs))
|
||||
show_stack(NULL, (unsigned long*)regs->areg[1]);
|
||||
|
||||
add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE);
|
||||
spin_unlock_irq(&die_lock);
|
||||
|
||||
if (in_interrupt())
|
||||
panic("Fatal exception in interrupt");
|
||||
|
||||
if (panic_on_oops)
|
||||
panic("Fatal exception");
|
||||
|
||||
do_exit(err);
|
||||
}
|
782
arch/xtensa/kernel/vectors.S
Normal file
782
arch/xtensa/kernel/vectors.S
Normal file
|
@ -0,0 +1,782 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/vectors.S
|
||||
*
|
||||
* This file contains all exception vectors (user, kernel, and double),
|
||||
* as well as the window vectors (overflow and underflow), and the debug
|
||||
* vector. These are the primary vectors executed by the processor if an
|
||||
* exception occurs.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General
|
||||
* Public License. See the file "COPYING" in the main directory of
|
||||
* this archive for more details.
|
||||
*
|
||||
* Copyright (C) 2005 - 2008 Tensilica, Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* We use a two-level table approach. The user and kernel exception vectors
|
||||
* use a first-level dispatch table to dispatch the exception to a registered
|
||||
* fast handler or the default handler, if no fast handler was registered.
|
||||
* The default handler sets up a C-stack and dispatches the exception to a
|
||||
* registerd C handler in the second-level dispatch table.
|
||||
*
|
||||
* Fast handler entry condition:
|
||||
*
|
||||
* a0: trashed, original value saved on stack (PT_AREG0)
|
||||
* a1: a1
|
||||
* a2: new stack pointer, original value in depc
|
||||
* a3: dispatch table
|
||||
* depc: a2, original value saved on stack (PT_DEPC)
|
||||
* excsave_1: a3
|
||||
*
|
||||
* The value for PT_DEPC saved to stack also functions as a boolean to
|
||||
* indicate that the exception is either a double or a regular exception:
|
||||
*
|
||||
* PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception
|
||||
* < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception
|
||||
*
|
||||
* Note: Neither the kernel nor the user exception handler generate literals.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/current.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/vectors.h>
|
||||
|
||||
#define WINDOW_VECTORS_SIZE 0x180
|
||||
|
||||
|
||||
/*
|
||||
* User exception vector. (Exceptions with PS.UM == 1, PS.EXCM == 0)
|
||||
*
|
||||
* We get here when an exception occurred while we were in userland.
|
||||
* We switch to the kernel stack and jump to the first level handler
|
||||
* associated to the exception cause.
|
||||
*
|
||||
* Note: the saved kernel stack pointer (EXC_TABLE_KSTK) is already
|
||||
* decremented by PT_USER_SIZE.
|
||||
*/
|
||||
|
||||
.section .UserExceptionVector.text, "ax"
|
||||
|
||||
ENTRY(_UserExceptionVector)
|
||||
|
||||
xsr a3, excsave1 # save a3 and get dispatch table
|
||||
wsr a2, depc # save a2
|
||||
l32i a2, a3, EXC_TABLE_KSTK # load kernel stack to a2
|
||||
s32i a0, a2, PT_AREG0 # save a0 to ESF
|
||||
rsr a0, exccause # retrieve exception cause
|
||||
s32i a0, a2, PT_DEPC # mark it as a regular exception
|
||||
addx4 a0, a0, a3 # find entry in table
|
||||
l32i a0, a0, EXC_TABLE_FAST_USER # load handler
|
||||
xsr a3, excsave1 # restore a3 and dispatch table
|
||||
jx a0
|
||||
|
||||
ENDPROC(_UserExceptionVector)
|
||||
|
||||
/*
|
||||
* Kernel exception vector. (Exceptions with PS.UM == 0, PS.EXCM == 0)
|
||||
*
|
||||
* We get this exception when we were already in kernel space.
|
||||
* We decrement the current stack pointer (kernel) by PT_SIZE and
|
||||
* jump to the first-level handler associated with the exception cause.
|
||||
*
|
||||
* Note: we need to preserve space for the spill region.
|
||||
*/
|
||||
|
||||
.section .KernelExceptionVector.text, "ax"
|
||||
|
||||
ENTRY(_KernelExceptionVector)
|
||||
|
||||
xsr a3, excsave1 # save a3, and get dispatch table
|
||||
wsr a2, depc # save a2
|
||||
addi a2, a1, -16-PT_SIZE # adjust stack pointer
|
||||
s32i a0, a2, PT_AREG0 # save a0 to ESF
|
||||
rsr a0, exccause # retrieve exception cause
|
||||
s32i a0, a2, PT_DEPC # mark it as a regular exception
|
||||
addx4 a0, a0, a3 # find entry in table
|
||||
l32i a0, a0, EXC_TABLE_FAST_KERNEL # load handler address
|
||||
xsr a3, excsave1 # restore a3 and dispatch table
|
||||
jx a0
|
||||
|
||||
ENDPROC(_KernelExceptionVector)
|
||||
|
||||
/*
|
||||
* Double exception vector (Exceptions with PS.EXCM == 1)
|
||||
* We get this exception when another exception occurs while were are
|
||||
* already in an exception, such as window overflow/underflow exception,
|
||||
* or 'expected' exceptions, for example memory exception when we were trying
|
||||
* to read data from an invalid address in user space.
|
||||
*
|
||||
* Note that this vector is never invoked for level-1 interrupts, because such
|
||||
* interrupts are disabled (masked) when PS.EXCM is set.
|
||||
*
|
||||
* We decode the exception and take the appropriate action. However, the
|
||||
* double exception vector is much more careful, because a lot more error
|
||||
* cases go through the double exception vector than through the user and
|
||||
* kernel exception vectors.
|
||||
*
|
||||
* Occasionally, the kernel expects a double exception to occur. This usually
|
||||
* happens when accessing user-space memory with the user's permissions
|
||||
* (l32e/s32e instructions). The kernel state, though, is not always suitable
|
||||
* for immediate transfer of control to handle_double, where "normal" exception
|
||||
* processing occurs. Also in kernel mode, TLB misses can occur if accessing
|
||||
* vmalloc memory, possibly requiring repair in a double exception handler.
|
||||
*
|
||||
* The variable at TABLE_FIXUP offset from the pointer in EXCSAVE_1 doubles as
|
||||
* a boolean variable and a pointer to a fixup routine. If the variable
|
||||
* EXC_TABLE_FIXUP is non-zero, this handler jumps to that address. A value of
|
||||
* zero indicates to use the default kernel/user exception handler.
|
||||
* There is only one exception, when the value is identical to the exc_table
|
||||
* label, the kernel is in trouble. This mechanism is used to protect critical
|
||||
* sections, mainly when the handler writes to the stack to assert the stack
|
||||
* pointer is valid. Once the fixup/default handler leaves that area, the
|
||||
* EXC_TABLE_FIXUP variable is reset to the fixup handler or zero.
|
||||
*
|
||||
* Procedures wishing to use this mechanism should set EXC_TABLE_FIXUP to the
|
||||
* nonzero address of a fixup routine before it could cause a double exception
|
||||
* and reset it before it returns.
|
||||
*
|
||||
* Some other things to take care of when a fast exception handler doesn't
|
||||
* specify a particular fixup handler but wants to use the default handlers:
|
||||
*
|
||||
* - The original stack pointer (in a1) must not be modified. The fast
|
||||
* exception handler should only use a2 as the stack pointer.
|
||||
*
|
||||
* - If the fast handler manipulates the stack pointer (in a2), it has to
|
||||
* register a valid fixup handler and cannot use the default handlers.
|
||||
*
|
||||
* - The handler can use any other generic register from a3 to a15, but it
|
||||
* must save the content of these registers to stack (PT_AREG3...PT_AREGx)
|
||||
*
|
||||
* - These registers must be saved before a double exception can occur.
|
||||
*
|
||||
* - If we ever implement handling signals while in double exceptions, the
|
||||
* number of registers a fast handler has saved (excluding a0 and a1) must
|
||||
* be written to PT_AREG1. (1 if only a3 is used, 2 for a3 and a4, etc. )
|
||||
*
|
||||
* The fixup handlers are special handlers:
|
||||
*
|
||||
* - Fixup entry conditions differ from regular exceptions:
|
||||
*
|
||||
* a0: DEPC
|
||||
* a1: a1
|
||||
* a2: trashed, original value in EXC_TABLE_DOUBLE_SAVE
|
||||
* a3: exctable
|
||||
* depc: a0
|
||||
* excsave_1: a3
|
||||
*
|
||||
* - When the kernel enters the fixup handler, it still assumes it is in a
|
||||
* critical section, so EXC_TABLE_FIXUP variable is set to exc_table.
|
||||
* The fixup handler, therefore, has to re-register itself as the fixup
|
||||
* handler before it returns from the double exception.
|
||||
*
|
||||
* - Fixup handler can share the same exception frame with the fast handler.
|
||||
* The kernel stack pointer is not changed when entering the fixup handler.
|
||||
*
|
||||
* - Fixup handlers can jump to the default kernel and user exception
|
||||
* handlers. Before it jumps, though, it has to setup a exception frame
|
||||
* on stack. Because the default handler resets the register fixup handler
|
||||
* the fixup handler must make sure that the default handler returns to
|
||||
* it instead of the exception address, so it can re-register itself as
|
||||
* the fixup handler.
|
||||
*
|
||||
* In case of a critical condition where the kernel cannot recover, we jump
|
||||
* to unrecoverable_exception with the following entry conditions.
|
||||
* All registers a0...a15 are unchanged from the last exception, except:
|
||||
*
|
||||
* a0: last address before we jumped to the unrecoverable_exception.
|
||||
* excsave_1: a0
|
||||
*
|
||||
*
|
||||
* See the handle_alloca_user and spill_registers routines for example clients.
|
||||
*
|
||||
* FIXME: Note: we currently don't allow signal handling coming from a double
|
||||
* exception, so the item markt with (*) is not required.
|
||||
*/
|
||||
|
||||
.section .DoubleExceptionVector.text, "ax"
|
||||
.begin literal_prefix .DoubleExceptionVector
|
||||
.globl _DoubleExceptionVector_WindowUnderflow
|
||||
.globl _DoubleExceptionVector_WindowOverflow
|
||||
|
||||
ENTRY(_DoubleExceptionVector)
|
||||
|
||||
xsr a3, excsave1
|
||||
s32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
|
||||
/* Check for kernel double exception (usually fatal). */
|
||||
|
||||
rsr a2, ps
|
||||
_bbci.l a2, PS_UM_BIT, .Lksp
|
||||
|
||||
/* Check if we are currently handling a window exception. */
|
||||
/* Note: We don't need to indicate that we enter a critical section. */
|
||||
|
||||
xsr a0, depc # get DEPC, save a0
|
||||
|
||||
movi a2, WINDOW_VECTORS_VADDR
|
||||
_bltu a0, a2, .Lfixup
|
||||
addi a2, a2, WINDOW_VECTORS_SIZE
|
||||
_bgeu a0, a2, .Lfixup
|
||||
|
||||
/* Window overflow/underflow exception. Get stack pointer. */
|
||||
|
||||
l32i a2, a3, EXC_TABLE_KSTK
|
||||
|
||||
/* Check for overflow/underflow exception, jump if overflow. */
|
||||
|
||||
bbci.l a0, 6, _DoubleExceptionVector_WindowOverflow
|
||||
|
||||
/*
|
||||
* Restart window underflow exception.
|
||||
* Currently:
|
||||
* depc = orig a0,
|
||||
* a0 = orig DEPC,
|
||||
* a2 = new sp based on KSTK from exc_table
|
||||
* a3 = excsave_1
|
||||
* excsave_1 = orig a3
|
||||
*
|
||||
* We return to the instruction in user space that caused the window
|
||||
* underflow exception. Therefore, we change window base to the value
|
||||
* before we entered the window underflow exception and prepare the
|
||||
* registers to return as if we were coming from a regular exception
|
||||
* by changing depc (in a0).
|
||||
* Note: We can trash the current window frame (a0...a3) and depc!
|
||||
*/
|
||||
_DoubleExceptionVector_WindowUnderflow:
|
||||
xsr a3, excsave1
|
||||
wsr a2, depc # save stack pointer temporarily
|
||||
rsr a0, ps
|
||||
extui a0, a0, PS_OWB_SHIFT, PS_OWB_WIDTH
|
||||
wsr a0, windowbase
|
||||
rsync
|
||||
|
||||
/* We are now in the previous window frame. Save registers again. */
|
||||
|
||||
xsr a2, depc # save a2 and get stack pointer
|
||||
s32i a0, a2, PT_AREG0
|
||||
xsr a3, excsave1
|
||||
rsr a0, exccause
|
||||
s32i a0, a2, PT_DEPC # mark it as a regular exception
|
||||
addx4 a0, a0, a3
|
||||
xsr a3, excsave1
|
||||
l32i a0, a0, EXC_TABLE_FAST_USER
|
||||
jx a0
|
||||
|
||||
/*
|
||||
* We only allow the ITLB miss exception if we are in kernel space.
|
||||
* All other exceptions are unexpected and thus unrecoverable!
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_MMU
|
||||
.extern fast_second_level_miss_double_kernel
|
||||
|
||||
.Lksp: /* a0: a0, a1: a1, a2: a2, a3: trashed, depc: depc, excsave: a3 */
|
||||
|
||||
rsr a3, exccause
|
||||
beqi a3, EXCCAUSE_ITLB_MISS, 1f
|
||||
addi a3, a3, -EXCCAUSE_DTLB_MISS
|
||||
bnez a3, .Lunrecoverable
|
||||
1: movi a3, fast_second_level_miss_double_kernel
|
||||
jx a3
|
||||
#else
|
||||
.equ .Lksp, .Lunrecoverable
|
||||
#endif
|
||||
|
||||
/* Critical! We can't handle this situation. PANIC! */
|
||||
|
||||
.extern unrecoverable_exception
|
||||
|
||||
.Lunrecoverable_fixup:
|
||||
l32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
xsr a0, depc
|
||||
|
||||
.Lunrecoverable:
|
||||
rsr a3, excsave1
|
||||
wsr a0, excsave1
|
||||
movi a0, unrecoverable_exception
|
||||
callx0 a0
|
||||
|
||||
.Lfixup:/* Check for a fixup handler or if we were in a critical section. */
|
||||
|
||||
/* a0: depc, a1: a1, a2: trash, a3: exctable, depc: a0, excsave1: a3 */
|
||||
|
||||
/* Enter critical section. */
|
||||
|
||||
l32i a2, a3, EXC_TABLE_FIXUP
|
||||
s32i a3, a3, EXC_TABLE_FIXUP
|
||||
beq a2, a3, .Lunrecoverable_fixup # critical section
|
||||
beqz a2, .Ldflt # no handler was registered
|
||||
|
||||
/* a0: depc, a1: a1, a2: trash, a3: exctable, depc: a0, excsave: a3 */
|
||||
|
||||
jx a2
|
||||
|
||||
.Ldflt: /* Get stack pointer. */
|
||||
|
||||
l32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
addi a2, a2, -PT_USER_SIZE
|
||||
|
||||
/* a0: depc, a1: a1, a2: kstk, a3: exctable, depc: a0, excsave: a3 */
|
||||
|
||||
s32i a0, a2, PT_DEPC
|
||||
l32i a0, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
xsr a0, depc
|
||||
s32i a0, a2, PT_AREG0
|
||||
|
||||
/* a0: avail, a1: a1, a2: kstk, a3: exctable, depc: a2, excsave: a3 */
|
||||
|
||||
rsr a0, exccause
|
||||
addx4 a0, a0, a3
|
||||
xsr a3, excsave1
|
||||
l32i a0, a0, EXC_TABLE_FAST_USER
|
||||
jx a0
|
||||
|
||||
/*
|
||||
* Restart window OVERFLOW exception.
|
||||
* Currently:
|
||||
* depc = orig a0,
|
||||
* a0 = orig DEPC,
|
||||
* a2 = new sp based on KSTK from exc_table
|
||||
* a3 = EXCSAVE_1
|
||||
* excsave_1 = orig a3
|
||||
*
|
||||
* We return to the instruction in user space that caused the window
|
||||
* overflow exception. Therefore, we change window base to the value
|
||||
* before we entered the window overflow exception and prepare the
|
||||
* registers to return as if we were coming from a regular exception
|
||||
* by changing DEPC (in a0).
|
||||
*
|
||||
* NOTE: We CANNOT trash the current window frame (a0...a3), but we
|
||||
* can clobber depc.
|
||||
*
|
||||
* The tricky part here is that overflow8 and overflow12 handlers
|
||||
* save a0, then clobber a0. To restart the handler, we have to restore
|
||||
* a0 if the double exception was past the point where a0 was clobbered.
|
||||
*
|
||||
* To keep things simple, we take advantage of the fact all overflow
|
||||
* handlers save a0 in their very first instruction. If DEPC was past
|
||||
* that instruction, we can safely restore a0 from where it was saved
|
||||
* on the stack.
|
||||
*
|
||||
* a0: depc, a1: a1, a2: kstk, a3: exc_table, depc: a0, excsave1: a3
|
||||
*/
|
||||
_DoubleExceptionVector_WindowOverflow:
|
||||
extui a2, a0, 0, 6 # get offset into 64-byte vector handler
|
||||
beqz a2, 1f # if at start of vector, don't restore
|
||||
|
||||
addi a0, a0, -128
|
||||
bbsi.l a0, 8, 1f # don't restore except for overflow 8 and 12
|
||||
|
||||
/*
|
||||
* This fixup handler is for the extremely unlikely case where the
|
||||
* overflow handler's reference thru a0 gets a hardware TLB refill
|
||||
* that bumps out the (distinct, aliasing) TLB entry that mapped its
|
||||
* prior references thru a9/a13, and where our reference now thru
|
||||
* a9/a13 gets a 2nd-level miss exception (not hardware TLB refill).
|
||||
*/
|
||||
movi a2, window_overflow_restore_a0_fixup
|
||||
s32i a2, a3, EXC_TABLE_FIXUP
|
||||
l32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
xsr a3, excsave1
|
||||
|
||||
bbsi.l a0, 7, 2f
|
||||
|
||||
/*
|
||||
* Restore a0 as saved by _WindowOverflow8().
|
||||
*/
|
||||
|
||||
l32e a0, a9, -16
|
||||
wsr a0, depc # replace the saved a0
|
||||
j 3f
|
||||
|
||||
2:
|
||||
/*
|
||||
* Restore a0 as saved by _WindowOverflow12().
|
||||
*/
|
||||
|
||||
l32e a0, a13, -16
|
||||
wsr a0, depc # replace the saved a0
|
||||
3:
|
||||
xsr a3, excsave1
|
||||
movi a0, 0
|
||||
s32i a0, a3, EXC_TABLE_FIXUP
|
||||
s32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
1:
|
||||
/*
|
||||
* Restore WindowBase while leaving all address registers restored.
|
||||
* We have to use ROTW for this, because WSR.WINDOWBASE requires
|
||||
* an address register (which would prevent restore).
|
||||
*
|
||||
* Window Base goes from 0 ... 7 (Module 8)
|
||||
* Window Start is 8 bits; Ex: (0b1010 1010):0x55 from series of call4s
|
||||
*/
|
||||
|
||||
rsr a0, ps
|
||||
extui a0, a0, PS_OWB_SHIFT, PS_OWB_WIDTH
|
||||
rsr a2, windowbase
|
||||
sub a0, a2, a0
|
||||
extui a0, a0, 0, 3
|
||||
|
||||
l32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
xsr a3, excsave1
|
||||
beqi a0, 1, .L1pane
|
||||
beqi a0, 3, .L3pane
|
||||
|
||||
rsr a0, depc
|
||||
rotw -2
|
||||
|
||||
/*
|
||||
* We are now in the user code's original window frame.
|
||||
* Process the exception as a user exception as if it was
|
||||
* taken by the user code.
|
||||
*
|
||||
* This is similar to the user exception vector,
|
||||
* except that PT_DEPC isn't set to EXCCAUSE.
|
||||
*/
|
||||
1:
|
||||
xsr a3, excsave1
|
||||
wsr a2, depc
|
||||
l32i a2, a3, EXC_TABLE_KSTK
|
||||
s32i a0, a2, PT_AREG0
|
||||
rsr a0, exccause
|
||||
|
||||
s32i a0, a2, PT_DEPC
|
||||
|
||||
_DoubleExceptionVector_handle_exception:
|
||||
addi a0, a0, -EXCCAUSE_UNALIGNED
|
||||
beqz a0, 2f
|
||||
addx4 a0, a0, a3
|
||||
l32i a0, a0, EXC_TABLE_FAST_USER + 4 * EXCCAUSE_UNALIGNED
|
||||
xsr a3, excsave1
|
||||
jx a0
|
||||
2:
|
||||
movi a0, user_exception
|
||||
xsr a3, excsave1
|
||||
jx a0
|
||||
|
||||
.L1pane:
|
||||
rsr a0, depc
|
||||
rotw -1
|
||||
j 1b
|
||||
|
||||
.L3pane:
|
||||
rsr a0, depc
|
||||
rotw -3
|
||||
j 1b
|
||||
|
||||
|
||||
ENDPROC(_DoubleExceptionVector)
|
||||
|
||||
/*
|
||||
* Fixup handler for TLB miss in double exception handler for window owerflow.
|
||||
* We get here with windowbase set to the window that was being spilled and
|
||||
* a0 trashed. a0 bit 7 determines if this is a call8 (bit clear) or call12
|
||||
* (bit set) window.
|
||||
*
|
||||
* We do the following here:
|
||||
* - go to the original window retaining a0 value;
|
||||
* - set up exception stack to return back to appropriate a0 restore code
|
||||
* (we'll need to rotate window back and there's no place to save this
|
||||
* information, use different return address for that);
|
||||
* - handle the exception;
|
||||
* - go to the window that was being spilled;
|
||||
* - set up window_overflow_restore_a0_fixup as a fixup routine;
|
||||
* - reload a0;
|
||||
* - restore the original window;
|
||||
* - reset the default fixup routine;
|
||||
* - return to user. By the time we get to this fixup handler all information
|
||||
* about the conditions of the original double exception that happened in
|
||||
* the window overflow handler is lost, so we just return to userspace to
|
||||
* retry overflow from start.
|
||||
*
|
||||
* a0: value of depc, original value in depc
|
||||
* a2: trashed, original value in EXC_TABLE_DOUBLE_SAVE
|
||||
* a3: exctable, original value in excsave1
|
||||
*/
|
||||
|
||||
ENTRY(window_overflow_restore_a0_fixup)
|
||||
|
||||
rsr a0, ps
|
||||
extui a0, a0, PS_OWB_SHIFT, PS_OWB_WIDTH
|
||||
rsr a2, windowbase
|
||||
sub a0, a2, a0
|
||||
extui a0, a0, 0, 3
|
||||
l32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
xsr a3, excsave1
|
||||
|
||||
_beqi a0, 1, .Lhandle_1
|
||||
_beqi a0, 3, .Lhandle_3
|
||||
|
||||
.macro overflow_fixup_handle_exception_pane n
|
||||
|
||||
rsr a0, depc
|
||||
rotw -\n
|
||||
|
||||
xsr a3, excsave1
|
||||
wsr a2, depc
|
||||
l32i a2, a3, EXC_TABLE_KSTK
|
||||
s32i a0, a2, PT_AREG0
|
||||
|
||||
movi a0, .Lrestore_\n
|
||||
s32i a0, a2, PT_DEPC
|
||||
rsr a0, exccause
|
||||
j _DoubleExceptionVector_handle_exception
|
||||
|
||||
.endm
|
||||
|
||||
overflow_fixup_handle_exception_pane 2
|
||||
.Lhandle_1:
|
||||
overflow_fixup_handle_exception_pane 1
|
||||
.Lhandle_3:
|
||||
overflow_fixup_handle_exception_pane 3
|
||||
|
||||
.macro overflow_fixup_restore_a0_pane n
|
||||
|
||||
rotw \n
|
||||
/* Need to preserve a0 value here to be able to handle exception
|
||||
* that may occur on a0 reload from stack. It may occur because
|
||||
* TLB miss handler may not be atomic and pointer to page table
|
||||
* may be lost before we get here. There are no free registers,
|
||||
* so we need to use EXC_TABLE_DOUBLE_SAVE area.
|
||||
*/
|
||||
xsr a3, excsave1
|
||||
s32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
movi a2, window_overflow_restore_a0_fixup
|
||||
s32i a2, a3, EXC_TABLE_FIXUP
|
||||
l32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
xsr a3, excsave1
|
||||
bbsi.l a0, 7, 1f
|
||||
l32e a0, a9, -16
|
||||
j 2f
|
||||
1:
|
||||
l32e a0, a13, -16
|
||||
2:
|
||||
rotw -\n
|
||||
|
||||
.endm
|
||||
|
||||
.Lrestore_2:
|
||||
overflow_fixup_restore_a0_pane 2
|
||||
|
||||
.Lset_default_fixup:
|
||||
xsr a3, excsave1
|
||||
s32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
movi a2, 0
|
||||
s32i a2, a3, EXC_TABLE_FIXUP
|
||||
l32i a2, a3, EXC_TABLE_DOUBLE_SAVE
|
||||
xsr a3, excsave1
|
||||
rfe
|
||||
|
||||
.Lrestore_1:
|
||||
overflow_fixup_restore_a0_pane 1
|
||||
j .Lset_default_fixup
|
||||
.Lrestore_3:
|
||||
overflow_fixup_restore_a0_pane 3
|
||||
j .Lset_default_fixup
|
||||
|
||||
ENDPROC(window_overflow_restore_a0_fixup)
|
||||
|
||||
.end literal_prefix
|
||||
/*
|
||||
* Debug interrupt vector
|
||||
*
|
||||
* There is not much space here, so simply jump to another handler.
|
||||
* EXCSAVE[DEBUGLEVEL] has been set to that handler.
|
||||
*/
|
||||
|
||||
.section .DebugInterruptVector.text, "ax"
|
||||
|
||||
ENTRY(_DebugInterruptVector)
|
||||
|
||||
xsr a0, SREG_EXCSAVE + XCHAL_DEBUGLEVEL
|
||||
jx a0
|
||||
|
||||
ENDPROC(_DebugInterruptVector)
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Medium priority level interrupt vectors
|
||||
*
|
||||
* Each takes less than 16 (0x10) bytes, no literals, by placing
|
||||
* the extra 8 bytes that would otherwise be required in the window
|
||||
* vectors area where there is space. With relocatable vectors,
|
||||
* all vectors are within ~ 4 kB range of each other, so we can
|
||||
* simply jump (J) to another vector without having to use JX.
|
||||
*
|
||||
* common_exception code gets current IRQ level in PS.INTLEVEL
|
||||
* and preserves it for the IRQ handling time.
|
||||
*/
|
||||
|
||||
.macro irq_entry_level level
|
||||
|
||||
.if XCHAL_EXCM_LEVEL >= \level
|
||||
.section .Level\level\()InterruptVector.text, "ax"
|
||||
ENTRY(_Level\level\()InterruptVector)
|
||||
wsr a0, excsave2
|
||||
rsr a0, epc\level
|
||||
wsr a0, epc1
|
||||
movi a0, EXCCAUSE_LEVEL1_INTERRUPT
|
||||
wsr a0, exccause
|
||||
rsr a0, eps\level
|
||||
# branch to user or kernel vector
|
||||
j _SimulateUserKernelVectorException
|
||||
.endif
|
||||
|
||||
.endm
|
||||
|
||||
irq_entry_level 2
|
||||
irq_entry_level 3
|
||||
irq_entry_level 4
|
||||
irq_entry_level 5
|
||||
irq_entry_level 6
|
||||
|
||||
|
||||
/* Window overflow and underflow handlers.
|
||||
* The handlers must be 64 bytes apart, first starting with the underflow
|
||||
* handlers underflow-4 to underflow-12, then the overflow handlers
|
||||
* overflow-4 to overflow-12.
|
||||
*
|
||||
* Note: We rerun the underflow handlers if we hit an exception, so
|
||||
* we try to access any page that would cause a page fault early.
|
||||
*/
|
||||
|
||||
#define ENTRY_ALIGN64(name) \
|
||||
.globl name; \
|
||||
.align 64; \
|
||||
name:
|
||||
|
||||
.section .WindowVectors.text, "ax"
|
||||
|
||||
|
||||
/* 4-Register Window Overflow Vector (Handler) */
|
||||
|
||||
ENTRY_ALIGN64(_WindowOverflow4)
|
||||
|
||||
s32e a0, a5, -16
|
||||
s32e a1, a5, -12
|
||||
s32e a2, a5, -8
|
||||
s32e a3, a5, -4
|
||||
rfwo
|
||||
|
||||
ENDPROC(_WindowOverflow4)
|
||||
|
||||
|
||||
#if XCHAL_EXCM_LEVEL >= 2
|
||||
/* Not a window vector - but a convenient location
|
||||
* (where we know there's space) for continuation of
|
||||
* medium priority interrupt dispatch code.
|
||||
* On entry here, a0 contains PS, and EPC2 contains saved a0:
|
||||
*/
|
||||
.align 4
|
||||
_SimulateUserKernelVectorException:
|
||||
addi a0, a0, (1 << PS_EXCM_BIT)
|
||||
wsr a0, ps
|
||||
bbsi.l a0, PS_UM_BIT, 1f # branch if user mode
|
||||
rsr a0, excsave2 # restore a0
|
||||
j _KernelExceptionVector # simulate kernel vector exception
|
||||
1: rsr a0, excsave2 # restore a0
|
||||
j _UserExceptionVector # simulate user vector exception
|
||||
#endif
|
||||
|
||||
|
||||
/* 4-Register Window Underflow Vector (Handler) */
|
||||
|
||||
ENTRY_ALIGN64(_WindowUnderflow4)
|
||||
|
||||
l32e a0, a5, -16
|
||||
l32e a1, a5, -12
|
||||
l32e a2, a5, -8
|
||||
l32e a3, a5, -4
|
||||
rfwu
|
||||
|
||||
ENDPROC(_WindowUnderflow4)
|
||||
|
||||
/* 8-Register Window Overflow Vector (Handler) */
|
||||
|
||||
ENTRY_ALIGN64(_WindowOverflow8)
|
||||
|
||||
s32e a0, a9, -16
|
||||
l32e a0, a1, -12
|
||||
s32e a2, a9, -8
|
||||
s32e a1, a9, -12
|
||||
s32e a3, a9, -4
|
||||
s32e a4, a0, -32
|
||||
s32e a5, a0, -28
|
||||
s32e a6, a0, -24
|
||||
s32e a7, a0, -20
|
||||
rfwo
|
||||
|
||||
ENDPROC(_WindowOverflow8)
|
||||
|
||||
/* 8-Register Window Underflow Vector (Handler) */
|
||||
|
||||
ENTRY_ALIGN64(_WindowUnderflow8)
|
||||
|
||||
l32e a1, a9, -12
|
||||
l32e a0, a9, -16
|
||||
l32e a7, a1, -12
|
||||
l32e a2, a9, -8
|
||||
l32e a4, a7, -32
|
||||
l32e a3, a9, -4
|
||||
l32e a5, a7, -28
|
||||
l32e a6, a7, -24
|
||||
l32e a7, a7, -20
|
||||
rfwu
|
||||
|
||||
ENDPROC(_WindowUnderflow8)
|
||||
|
||||
/* 12-Register Window Overflow Vector (Handler) */
|
||||
|
||||
ENTRY_ALIGN64(_WindowOverflow12)
|
||||
|
||||
s32e a0, a13, -16
|
||||
l32e a0, a1, -12
|
||||
s32e a1, a13, -12
|
||||
s32e a2, a13, -8
|
||||
s32e a3, a13, -4
|
||||
s32e a4, a0, -48
|
||||
s32e a5, a0, -44
|
||||
s32e a6, a0, -40
|
||||
s32e a7, a0, -36
|
||||
s32e a8, a0, -32
|
||||
s32e a9, a0, -28
|
||||
s32e a10, a0, -24
|
||||
s32e a11, a0, -20
|
||||
rfwo
|
||||
|
||||
ENDPROC(_WindowOverflow12)
|
||||
|
||||
/* 12-Register Window Underflow Vector (Handler) */
|
||||
|
||||
ENTRY_ALIGN64(_WindowUnderflow12)
|
||||
|
||||
l32e a1, a13, -12
|
||||
l32e a0, a13, -16
|
||||
l32e a11, a1, -12
|
||||
l32e a2, a13, -8
|
||||
l32e a4, a11, -48
|
||||
l32e a8, a11, -32
|
||||
l32e a3, a13, -4
|
||||
l32e a5, a11, -44
|
||||
l32e a6, a11, -40
|
||||
l32e a7, a11, -36
|
||||
l32e a9, a11, -28
|
||||
l32e a10, a11, -24
|
||||
l32e a11, a11, -20
|
||||
rfwu
|
||||
|
||||
ENDPROC(_WindowUnderflow12)
|
||||
|
||||
.text
|
370
arch/xtensa/kernel/vmlinux.lds.S
Normal file
370
arch/xtensa/kernel/vmlinux.lds.S
Normal file
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/vmlinux.lds.S
|
||||
*
|
||||
* Xtensa linker script
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2008 Tensilica Inc.
|
||||
*
|
||||
* Chris Zankel <chris@zankel.net>
|
||||
* Marc Gauthier <marc@tensilica.com, marc@alumni.uwaterloo.ca>
|
||||
* Joe Taylor <joe@tensilica.com, joetylr@yahoo.com>
|
||||
*/
|
||||
|
||||
#include <asm-generic/vmlinux.lds.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/thread_info.h>
|
||||
|
||||
#include <asm/vectors.h>
|
||||
#include <variant/core.h>
|
||||
#include <platform/hardware.h>
|
||||
OUTPUT_ARCH(xtensa)
|
||||
ENTRY(_start)
|
||||
|
||||
#ifdef __XTENSA_EB__
|
||||
jiffies = jiffies_64 + 4;
|
||||
#else
|
||||
jiffies = jiffies_64;
|
||||
#endif
|
||||
|
||||
#ifndef KERNELOFFSET
|
||||
#define KERNELOFFSET 0xd0003000
|
||||
#endif
|
||||
|
||||
/* Note: In the following macros, it would be nice to specify only the
|
||||
vector name and section kind and construct "sym" and "section" using
|
||||
CPP concatenation, but that does not work reliably. Concatenating a
|
||||
string with "." produces an invalid token. CPP will not print a
|
||||
warning because it thinks this is an assembly file, but it leaves
|
||||
them as multiple tokens and there may or may not be whitespace
|
||||
between them. */
|
||||
|
||||
/* Macro for a relocation entry */
|
||||
|
||||
#define RELOCATE_ENTRY(sym, section) \
|
||||
LONG(sym ## _start); \
|
||||
LONG(sym ## _end); \
|
||||
LONG(LOADADDR(section))
|
||||
|
||||
/* Macro to define a section for a vector.
|
||||
*
|
||||
* Use of the MIN function catches the types of errors illustrated in
|
||||
* the following example:
|
||||
*
|
||||
* Assume the section .DoubleExceptionVector.literal is completely
|
||||
* full. Then a programmer adds code to .DoubleExceptionVector.text
|
||||
* that produces another literal. The final literal position will
|
||||
* overlay onto the first word of the adjacent code section
|
||||
* .DoubleExceptionVector.text. (In practice, the literals will
|
||||
* overwrite the code, and the first few instructions will be
|
||||
* garbage.)
|
||||
*/
|
||||
|
||||
#define SECTION_VECTOR(sym, section, addr, max_prevsec_size, prevsec) \
|
||||
section addr : AT((MIN(LOADADDR(prevsec) + max_prevsec_size, \
|
||||
LOADADDR(prevsec) + SIZEOF(prevsec)) + 3) & ~ 3) \
|
||||
{ \
|
||||
. = ALIGN(4); \
|
||||
sym ## _start = ABSOLUTE(.); \
|
||||
*(section) \
|
||||
sym ## _end = ABSOLUTE(.); \
|
||||
}
|
||||
|
||||
/*
|
||||
* Mapping of input sections to output sections when linking.
|
||||
*/
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = KERNELOFFSET;
|
||||
/* .text section */
|
||||
|
||||
_text = .;
|
||||
_stext = .;
|
||||
|
||||
.text :
|
||||
{
|
||||
/* The HEAD_TEXT section must be the first section! */
|
||||
HEAD_TEXT
|
||||
TEXT_TEXT
|
||||
VMLINUX_SYMBOL(__sched_text_start) = .;
|
||||
*(.sched.literal .sched.text)
|
||||
VMLINUX_SYMBOL(__sched_text_end) = .;
|
||||
VMLINUX_SYMBOL(__lock_text_start) = .;
|
||||
*(.spinlock.literal .spinlock.text)
|
||||
VMLINUX_SYMBOL(__lock_text_end) = .;
|
||||
|
||||
}
|
||||
_etext = .;
|
||||
PROVIDE (etext = .);
|
||||
|
||||
. = ALIGN(16);
|
||||
|
||||
RODATA
|
||||
|
||||
/* Relocation table */
|
||||
|
||||
.fixup : { *(.fixup) }
|
||||
|
||||
EXCEPTION_TABLE(16)
|
||||
/* Data section */
|
||||
|
||||
_sdata = .;
|
||||
RW_DATA_SECTION(XCHAL_ICACHE_LINESIZE, PAGE_SIZE, THREAD_SIZE)
|
||||
_edata = .;
|
||||
|
||||
/* Initialization code and data: */
|
||||
|
||||
. = ALIGN(PAGE_SIZE);
|
||||
__init_begin = .;
|
||||
INIT_TEXT_SECTION(PAGE_SIZE)
|
||||
|
||||
.init.data :
|
||||
{
|
||||
INIT_DATA
|
||||
. = ALIGN(0x4);
|
||||
__tagtable_begin = .;
|
||||
*(.taglist)
|
||||
__tagtable_end = .;
|
||||
|
||||
. = ALIGN(16);
|
||||
__boot_reloc_table_start = ABSOLUTE(.);
|
||||
|
||||
RELOCATE_ENTRY(_WindowVectors_text,
|
||||
.WindowVectors.text);
|
||||
#if XCHAL_EXCM_LEVEL >= 2
|
||||
RELOCATE_ENTRY(_Level2InterruptVector_text,
|
||||
.Level2InterruptVector.text);
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 3
|
||||
RELOCATE_ENTRY(_Level3InterruptVector_text,
|
||||
.Level3InterruptVector.text);
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 4
|
||||
RELOCATE_ENTRY(_Level4InterruptVector_text,
|
||||
.Level4InterruptVector.text);
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 5
|
||||
RELOCATE_ENTRY(_Level5InterruptVector_text,
|
||||
.Level5InterruptVector.text);
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 6
|
||||
RELOCATE_ENTRY(_Level6InterruptVector_text,
|
||||
.Level6InterruptVector.text);
|
||||
#endif
|
||||
RELOCATE_ENTRY(_KernelExceptionVector_text,
|
||||
.KernelExceptionVector.text);
|
||||
RELOCATE_ENTRY(_UserExceptionVector_text,
|
||||
.UserExceptionVector.text);
|
||||
RELOCATE_ENTRY(_DoubleExceptionVector_literal,
|
||||
.DoubleExceptionVector.literal);
|
||||
RELOCATE_ENTRY(_DoubleExceptionVector_text,
|
||||
.DoubleExceptionVector.text);
|
||||
RELOCATE_ENTRY(_DebugInterruptVector_text,
|
||||
.DebugInterruptVector.text);
|
||||
#if defined(CONFIG_SMP)
|
||||
RELOCATE_ENTRY(_SecondaryResetVector_literal,
|
||||
.SecondaryResetVector.literal);
|
||||
RELOCATE_ENTRY(_SecondaryResetVector_text,
|
||||
.SecondaryResetVector.text);
|
||||
#endif
|
||||
|
||||
|
||||
__boot_reloc_table_end = ABSOLUTE(.) ;
|
||||
|
||||
INIT_SETUP(XCHAL_ICACHE_LINESIZE)
|
||||
INIT_CALLS
|
||||
CON_INITCALL
|
||||
SECURITY_INITCALL
|
||||
INIT_RAM_FS
|
||||
}
|
||||
|
||||
PERCPU_SECTION(XCHAL_ICACHE_LINESIZE)
|
||||
|
||||
/* We need this dummy segment here */
|
||||
|
||||
. = ALIGN(4);
|
||||
.dummy : { LONG(0) }
|
||||
|
||||
/* The vectors are relocated to the real position at startup time */
|
||||
|
||||
SECTION_VECTOR (_WindowVectors_text,
|
||||
.WindowVectors.text,
|
||||
WINDOW_VECTORS_VADDR, 4,
|
||||
.dummy)
|
||||
SECTION_VECTOR (_DebugInterruptVector_literal,
|
||||
.DebugInterruptVector.literal,
|
||||
DEBUG_VECTOR_VADDR - 4,
|
||||
SIZEOF(.WindowVectors.text),
|
||||
.WindowVectors.text)
|
||||
SECTION_VECTOR (_DebugInterruptVector_text,
|
||||
.DebugInterruptVector.text,
|
||||
DEBUG_VECTOR_VADDR,
|
||||
4,
|
||||
.DebugInterruptVector.literal)
|
||||
#undef LAST
|
||||
#define LAST .DebugInterruptVector.text
|
||||
#if XCHAL_EXCM_LEVEL >= 2
|
||||
SECTION_VECTOR (_Level2InterruptVector_text,
|
||||
.Level2InterruptVector.text,
|
||||
INTLEVEL2_VECTOR_VADDR,
|
||||
SIZEOF(LAST), LAST)
|
||||
# undef LAST
|
||||
# define LAST .Level2InterruptVector.text
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 3
|
||||
SECTION_VECTOR (_Level3InterruptVector_text,
|
||||
.Level3InterruptVector.text,
|
||||
INTLEVEL3_VECTOR_VADDR,
|
||||
SIZEOF(LAST), LAST)
|
||||
# undef LAST
|
||||
# define LAST .Level3InterruptVector.text
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 4
|
||||
SECTION_VECTOR (_Level4InterruptVector_text,
|
||||
.Level4InterruptVector.text,
|
||||
INTLEVEL4_VECTOR_VADDR,
|
||||
SIZEOF(LAST), LAST)
|
||||
# undef LAST
|
||||
# define LAST .Level4InterruptVector.text
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 5
|
||||
SECTION_VECTOR (_Level5InterruptVector_text,
|
||||
.Level5InterruptVector.text,
|
||||
INTLEVEL5_VECTOR_VADDR,
|
||||
SIZEOF(LAST), LAST)
|
||||
# undef LAST
|
||||
# define LAST .Level5InterruptVector.text
|
||||
#endif
|
||||
#if XCHAL_EXCM_LEVEL >= 6
|
||||
SECTION_VECTOR (_Level6InterruptVector_text,
|
||||
.Level6InterruptVector.text,
|
||||
INTLEVEL6_VECTOR_VADDR,
|
||||
SIZEOF(LAST), LAST)
|
||||
# undef LAST
|
||||
# define LAST .Level6InterruptVector.text
|
||||
#endif
|
||||
SECTION_VECTOR (_KernelExceptionVector_literal,
|
||||
.KernelExceptionVector.literal,
|
||||
KERNEL_VECTOR_VADDR - 4,
|
||||
SIZEOF(LAST), LAST)
|
||||
#undef LAST
|
||||
SECTION_VECTOR (_KernelExceptionVector_text,
|
||||
.KernelExceptionVector.text,
|
||||
KERNEL_VECTOR_VADDR,
|
||||
4,
|
||||
.KernelExceptionVector.literal)
|
||||
SECTION_VECTOR (_UserExceptionVector_literal,
|
||||
.UserExceptionVector.literal,
|
||||
USER_VECTOR_VADDR - 4,
|
||||
SIZEOF(.KernelExceptionVector.text),
|
||||
.KernelExceptionVector.text)
|
||||
SECTION_VECTOR (_UserExceptionVector_text,
|
||||
.UserExceptionVector.text,
|
||||
USER_VECTOR_VADDR,
|
||||
4,
|
||||
.UserExceptionVector.literal)
|
||||
SECTION_VECTOR (_DoubleExceptionVector_literal,
|
||||
.DoubleExceptionVector.literal,
|
||||
DOUBLEEXC_VECTOR_VADDR - 48,
|
||||
SIZEOF(.UserExceptionVector.text),
|
||||
.UserExceptionVector.text)
|
||||
SECTION_VECTOR (_DoubleExceptionVector_text,
|
||||
.DoubleExceptionVector.text,
|
||||
DOUBLEEXC_VECTOR_VADDR,
|
||||
48,
|
||||
.DoubleExceptionVector.literal)
|
||||
|
||||
. = (LOADADDR( .DoubleExceptionVector.text ) + SIZEOF( .DoubleExceptionVector.text ) + 3) & ~ 3;
|
||||
|
||||
#if defined(CONFIG_SMP)
|
||||
|
||||
SECTION_VECTOR (_SecondaryResetVector_literal,
|
||||
.SecondaryResetVector.literal,
|
||||
RESET_VECTOR1_VADDR - 4,
|
||||
SIZEOF(.DoubleExceptionVector.text),
|
||||
.DoubleExceptionVector.text)
|
||||
|
||||
SECTION_VECTOR (_SecondaryResetVector_text,
|
||||
.SecondaryResetVector.text,
|
||||
RESET_VECTOR1_VADDR,
|
||||
4,
|
||||
.SecondaryResetVector.literal)
|
||||
|
||||
. = LOADADDR(.SecondaryResetVector.text)+SIZEOF(.SecondaryResetVector.text);
|
||||
|
||||
#endif
|
||||
|
||||
. = ALIGN(PAGE_SIZE);
|
||||
|
||||
__init_end = .;
|
||||
|
||||
BSS_SECTION(0, 8192, 0)
|
||||
|
||||
_end = .;
|
||||
|
||||
/* only used by the boot loader */
|
||||
|
||||
. = ALIGN(0x10);
|
||||
.bootstrap : { *(.bootstrap.literal .bootstrap.text .bootstrap.data) }
|
||||
|
||||
.ResetVector.text RESET_VECTOR_VADDR :
|
||||
{
|
||||
*(.ResetVector.text)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This is a remapped copy of the Secondary Reset Vector Code.
|
||||
* It keeps gdb in sync with the PC after switching
|
||||
* to the temporary mapping used while setting up
|
||||
* the V2 MMU mappings for Linux.
|
||||
*
|
||||
* Only debug information about this section is put in the kernel image.
|
||||
*/
|
||||
.SecondaryResetVector.remapped_text 0x46000000 (INFO):
|
||||
{
|
||||
*(.SecondaryResetVector.remapped_text)
|
||||
}
|
||||
|
||||
|
||||
.xt.lit : { *(.xt.lit) }
|
||||
.xt.prop : { *(.xt.prop) }
|
||||
|
||||
.debug 0 : { *(.debug) }
|
||||
.line 0 : { *(.line) }
|
||||
.debug_srcinfo 0 : { *(.debug_srcinfo) }
|
||||
.debug_sfnames 0 : { *(.debug_sfnames) }
|
||||
.debug_aranges 0 : { *(.debug_aranges) }
|
||||
.debug_pubnames 0 : { *(.debug_pubnames) }
|
||||
.debug_info 0 : { *(.debug_info) }
|
||||
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||
.debug_line 0 : { *(.debug_line) }
|
||||
.debug_frame 0 : { *(.debug_frame) }
|
||||
.debug_str 0 : { *(.debug_str) }
|
||||
.debug_loc 0 : { *(.debug_loc) }
|
||||
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||
.debug_weaknames 0 : { *(.debug_weaknames) }
|
||||
.debug_funcnames 0 : { *(.debug_funcnames) }
|
||||
.debug_typenames 0 : { *(.debug_typenames) }
|
||||
.debug_varnames 0 : { *(.debug_varnames) }
|
||||
|
||||
.xt.insn 0 :
|
||||
{
|
||||
*(.xt.insn)
|
||||
*(.gnu.linkonce.x*)
|
||||
}
|
||||
|
||||
.xt.lit 0 :
|
||||
{
|
||||
*(.xt.lit)
|
||||
*(.gnu.linkonce.p*)
|
||||
}
|
||||
|
||||
/* Sections to be discarded */
|
||||
DISCARDS
|
||||
/DISCARD/ : { *(.exit.literal) }
|
||||
}
|
136
arch/xtensa/kernel/xtensa_ksyms.c
Normal file
136
arch/xtensa/kernel/xtensa_ksyms.c
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* arch/xtensa/kernel/xtensa_ksyms.c
|
||||
*
|
||||
* Export Xtensa-specific functions for loadable modules.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2001 - 2005 Tensilica Inc.
|
||||
*
|
||||
* Joe Taylor <joe@tensilica.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <asm/irq.h>
|
||||
#include <linux/in6.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/checksum.h>
|
||||
#include <asm/dma.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/pgalloc.h>
|
||||
#include <asm/ftrace.h>
|
||||
#ifdef CONFIG_BLK_DEV_FD
|
||||
#include <asm/floppy.h>
|
||||
#endif
|
||||
#ifdef CONFIG_NET
|
||||
#include <net/checksum.h>
|
||||
#endif /* CONFIG_NET */
|
||||
|
||||
|
||||
/*
|
||||
* String functions
|
||||
*/
|
||||
EXPORT_SYMBOL(memset);
|
||||
EXPORT_SYMBOL(memcpy);
|
||||
EXPORT_SYMBOL(memmove);
|
||||
EXPORT_SYMBOL(__strncpy_user);
|
||||
EXPORT_SYMBOL(clear_page);
|
||||
EXPORT_SYMBOL(copy_page);
|
||||
|
||||
EXPORT_SYMBOL(empty_zero_page);
|
||||
|
||||
/*
|
||||
* gcc internal math functions
|
||||
*/
|
||||
extern long long __ashrdi3(long long, int);
|
||||
extern long long __ashldi3(long long, int);
|
||||
extern long long __lshrdi3(long long, int);
|
||||
extern int __divsi3(int, int);
|
||||
extern int __modsi3(int, int);
|
||||
extern long long __muldi3(long long, long long);
|
||||
extern int __mulsi3(int, int);
|
||||
extern unsigned int __udivsi3(unsigned int, unsigned int);
|
||||
extern unsigned int __umodsi3(unsigned int, unsigned int);
|
||||
extern unsigned long long __umoddi3(unsigned long long, unsigned long long);
|
||||
extern unsigned long long __udivdi3(unsigned long long, unsigned long long);
|
||||
extern int __ucmpdi2(int, int);
|
||||
|
||||
EXPORT_SYMBOL(__ashldi3);
|
||||
EXPORT_SYMBOL(__ashrdi3);
|
||||
EXPORT_SYMBOL(__lshrdi3);
|
||||
EXPORT_SYMBOL(__divsi3);
|
||||
EXPORT_SYMBOL(__modsi3);
|
||||
EXPORT_SYMBOL(__muldi3);
|
||||
EXPORT_SYMBOL(__mulsi3);
|
||||
EXPORT_SYMBOL(__udivsi3);
|
||||
EXPORT_SYMBOL(__umodsi3);
|
||||
EXPORT_SYMBOL(__udivdi3);
|
||||
EXPORT_SYMBOL(__umoddi3);
|
||||
EXPORT_SYMBOL(__ucmpdi2);
|
||||
|
||||
void __xtensa_libgcc_window_spill(void)
|
||||
{
|
||||
BUG();
|
||||
}
|
||||
EXPORT_SYMBOL(__xtensa_libgcc_window_spill);
|
||||
|
||||
unsigned long __sync_fetch_and_and_4(unsigned long *p, unsigned long v)
|
||||
{
|
||||
BUG();
|
||||
}
|
||||
EXPORT_SYMBOL(__sync_fetch_and_and_4);
|
||||
|
||||
unsigned long __sync_fetch_and_or_4(unsigned long *p, unsigned long v)
|
||||
{
|
||||
BUG();
|
||||
}
|
||||
EXPORT_SYMBOL(__sync_fetch_and_or_4);
|
||||
|
||||
#ifdef CONFIG_NET
|
||||
/*
|
||||
* Networking support
|
||||
*/
|
||||
EXPORT_SYMBOL(csum_partial);
|
||||
EXPORT_SYMBOL(csum_partial_copy_generic);
|
||||
#endif /* CONFIG_NET */
|
||||
|
||||
/*
|
||||
* Architecture-specific symbols
|
||||
*/
|
||||
EXPORT_SYMBOL(__xtensa_copy_user);
|
||||
EXPORT_SYMBOL(__invalidate_icache_range);
|
||||
|
||||
/*
|
||||
* Kernel hacking ...
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE)
|
||||
// FIXME EXPORT_SYMBOL(screen_info);
|
||||
#endif
|
||||
|
||||
EXPORT_SYMBOL(outsb);
|
||||
EXPORT_SYMBOL(outsw);
|
||||
EXPORT_SYMBOL(outsl);
|
||||
EXPORT_SYMBOL(insb);
|
||||
EXPORT_SYMBOL(insw);
|
||||
EXPORT_SYMBOL(insl);
|
||||
|
||||
extern long common_exception_return;
|
||||
EXPORT_SYMBOL(common_exception_return);
|
||||
|
||||
#ifdef CONFIG_FUNCTION_TRACER
|
||||
EXPORT_SYMBOL(_mcount);
|
||||
#endif
|
||||
|
||||
EXPORT_SYMBOL(__invalidate_dcache_range);
|
||||
#if XCHAL_DCACHE_IS_WRITEBACK
|
||||
EXPORT_SYMBOL(__flush_dcache_range);
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue