From: Chris Zankel Date: Fri, 24 Jun 2005 05:01:16 +0000 (-0700) Subject: [PATCH] xtensa: Architecture support for Tensilica Xtensa Part 3 X-Git-Tag: v2.6.13-rc1~68^2~366 X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5a0015d62668e64c8b6e02e360fbbea121bfd5e6;p=linux-2.6 [PATCH] xtensa: Architecture support for Tensilica Xtensa Part 3 The attached patches provides part 3 of an architecture implementation for the Tensilica Xtensa CPU series. Signed-off-by: Chris Zankel Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- diff --git a/arch/xtensa/kernel/Makefile b/arch/xtensa/kernel/Makefile new file mode 100644 index 0000000000..d573017a5d --- /dev/null +++ b/arch/xtensa/kernel/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the Linux/Xtensa kernel. +# + +extra-y := head.o vmlinux.lds + + +obj-y := align.o entry.o irq.o coprocessor.o process.o ptrace.o semaphore.o \ + setup.o signal.o syscalls.o time.o traps.o vectors.o platform.o \ + pci-dma.o + +## windowspill.o + +obj-$(CONFIG_KGDB) += xtensa-stub.o +obj-$(CONFIG_PCI) += pci.o +obj-$(CONFIG_MODULES) += xtensa_ksyms.o module.o + + diff --git a/arch/xtensa/kernel/align.S b/arch/xtensa/kernel/align.S new file mode 100644 index 0000000000..74b1e90ef0 --- /dev/null +++ b/arch/xtensa/kernel/align.S @@ -0,0 +1,459 @@ +/* + * 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. + * + * Rewritten by Chris Zankel + * + * Based on work from Joe Taylor + * and Marc Gauthier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + * + * 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 + xsr a3, EXCSAVE_1 + s32i a0, a2, PT_AREG2 + s32i a3, a2, PT_AREG3 + + /* 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, EPC_1 # 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_NARROW + _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 + + /* Invalid instruction, CRITICAL! */ +.Linvalid_instruction_load: + j .Linvalid_instruction + + /* 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_NARROW + 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: + +#if XCHAL_HAVE_LOOP + rsr a3, LEND # check if we reached LEND + bne a7, a3, 1f + rsr a3, LCOUNT # and LCOUNT != 0 + beqz a3, 1f + addi a3, a3, -1 # decrement LCOUNT and set + rsr a7, LBEG # set PC to LBEGIN + wsr a3, LCOUNT +#endif + +1: wsr a7, EPC_1 # skip load instruction + 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 + +1: # a7: instruction pointer, a4: instruction, a3: value + + movi a6, 0 # mask: ffffffff:00000000 + +#if XCHAL_HAVE_NARROW + 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: +#if XCHAL_HAVE_LOOP + rsr a3, LEND # check if we reached LEND + bne a7, a3, 1f + rsr a3, LCOUNT # and LCOUNT != 0 + beqz a3, 1f + addi a3, a3, -1 # decrement LCOUNT and set + rsr a7, LBEG # set PC to LBEGIN + wsr a3, LCOUNT +#endif + +1: wsr a7, EPC_1 # skip store instruction + 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 a7, 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, a7 # mask + __sh a7, a3 # shift value + or a5, a5, a7 # or with original value +#ifdef UNALIGNED_USER_EXCEPTION + s32e a5, a4, -8 + l32e a7, a4, -4 +#else + s32i a5, a4, 0 # store + l32i a7, a4, 4 # same for upper address word +#endif + __sl a5, a3 + and a6, a7, a6 + or a6, a6, a5 +#ifdef UNALIGNED_USER_EXCEPTION + s32e a6, a4, -4 +#else + s32i a6, a4, 4 +#endif + + /* Done. restore stack and return */ + +.Lexit: + movi a4, 0 + rsr a3, EXCSAVE_1 + s32i a4, a3, EXC_TABLE_FIXUP + + /* Restore working register */ + + 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 + + /* We cannot handle this exception. */ + + .extern _kernel_exception +.Linvalid_instruction_store: +.Linvalid_instruction: + + /* 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 a2, PS_UM_SHIFT, 1f # jump if user mode + + movi a0, _kernel_exception + jx a0 + +1: movi a0, _user_exception + jx a0 + + +#endif /* XCHAL_UNALIGNED_LOAD_EXCEPTION || XCHAL_UNALIGNED_STORE_EXCEPTION */ + diff --git a/arch/xtensa/kernel/asm-offsets.c b/arch/xtensa/kernel/asm-offsets.c new file mode 100644 index 0000000000..840cd9a1d3 --- /dev/null +++ b/arch/xtensa/kernel/asm-offsets.c @@ -0,0 +1,94 @@ +/* + * 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 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFINE(sym, val) asm volatile("\n->" #sym " %0 " #val : : "i" (val)) +#define BLANK() asm volatile("\n->" : : ) + +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_SYSCALL, offsetof (struct pt_regs, syscall)); + 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])); + BLANK(); + + /* 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, thread_info)); + DEFINE(TASK_STRUCT_SIZE, sizeof (struct task_struct)); + BLANK(); + + /* 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_CP_SAVE, offsetof (struct task_struct, thread.cp_save)); + DEFINE(THREAD_CURRENT_DS, offsetof (struct task_struct, thread.current_ds)); + BLANK(); + + /* 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)); + BLANK(); + DEFINE(PT_SINGLESTEP_BIT, PT_SINGLESTEP_BIT); + return 0; +} + + diff --git a/arch/xtensa/kernel/coprocessor.S b/arch/xtensa/kernel/coprocessor.S new file mode 100644 index 0000000000..356192a4d3 --- /dev/null +++ b/arch/xtensa/kernel/coprocessor.S @@ -0,0 +1,201 @@ +/* + * 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 - 2005 Tensilica Inc. + * + * Marc Gauthier + */ + +/* + * This module contains a table that describes the layout of the various + * custom registers and states associated with each coprocessor, as well + * as those not associated with any coprocessor ("extra state"). + * This table is included with core dumps and is available via the ptrace + * interface, allowing the layout of such register/state information to + * be modified in the kernel without affecting the debugger. Each + * register or state is identified using a 32-bit "libdb target number" + * assigned when the Xtensa processor is generated. + */ + +#include +#include +#include + +#if XCHAL_HAVE_CP + +#define CP_LAST ((XCHAL_CP_MAX - 1) * COPROCESSOR_INFO_SIZE) + +ENTRY(release_coprocessors) + + entry a1, 16 + # a2: task + movi a3, 1 << XCHAL_CP_MAX # a3: coprocessor-bit + movi a4, coprocessor_info+CP_LAST # a4: owner-table + # a5: tmp + movi a6, 0 # a6: 0 + rsil a7, LOCKLEVEL # a7: PS + +1: /* Check if task is coprocessor owner of coprocessor[i]. */ + + l32i a5, a4, COPROCESSOR_INFO_OWNER + srli a3, a3, 1 + beqz a3, 1f + addi a4, a4, -8 + beq a2, a5, 1b + + /* Found an entry: Clear entry CPENABLE bit to disable CP. */ + + rsr a5, CPENABLE + s32i a6, a4, COPROCESSOR_INFO_OWNER + xor a5, a3, a5 + wsr a5, CPENABLE + + bnez a3, 1b + +1: wsr a7, PS + rsync + retw + + +ENTRY(disable_coprocessor) + entry sp, 16 + rsil a7, LOCKLEVEL + rsr a3, CPENABLE + movi a4, 1 + ssl a2 + sll a4, a4 + and a4, a3, a4 + xor a3, a3, a4 + wsr a3, CPENABLE + wsr a7, PS + rsync + retw + +ENTRY(enable_coprocessor) + entry sp, 16 + rsil a7, LOCKLEVEL + rsr a3, CPENABLE + movi a4, 1 + ssl a2 + sll a4, a4 + or a3, a3, a4 + wsr a3, CPENABLE + wsr a7, PS + rsync + retw + +#endif + +ENTRY(save_coprocessor_extra) + entry sp, 16 + xchal_extra_store_funcbody + retw + +ENTRY(restore_coprocessor_extra) + entry sp, 16 + xchal_extra_load_funcbody + retw + +ENTRY(save_coprocessor_registers) + entry sp, 16 + xchal_cpi_store_funcbody + retw + +ENTRY(restore_coprocessor_registers) + entry sp, 16 + xchal_cpi_load_funcbody + retw + + +/* + * The Xtensa compile-time HAL (core.h) XCHAL_*_SA_CONTENTS_LIBDB macros + * describe the contents of coprocessor & extra save areas in terms of + * undefined CONTENTS_LIBDB_{SREG,UREG,REGF} macros. We define these + * latter macros here; they expand into a table of the format we want. + * The general format is: + * + * CONTENTS_LIBDB_SREG(libdbnum, offset, size, align, rsv1, name, sregnum, + * bitmask, rsv2, rsv3) + * CONTENTS_LIBDB_UREG(libdbnum, offset, size, align, rsv1, name, uregnum, + * bitmask, rsv2, rsv3) + * CONTENTS_LIBDB_REGF(libdbnum, offset, size, align, rsv1, name, index, + * numentries, contentsize, regname_base, + * regfile_name, rsv2, rsv3) + * + * For this table, we only care about the , and + * fields. + */ + +/* Map all XCHAL CONTENTS macros to the reg_entry asm macro defined below: */ + +#define CONTENTS_LIBDB_SREG(libdbnum,offset,size,align,rsv1,name,sregnum, \ + bitmask, rsv2, rsv3) \ + reg_entry libdbnum, offset, size ; +#define CONTENTS_LIBDB_UREG(libdbnum,offset,size,align,rsv1,name,uregnum, \ + bitmask, rsv2, rsv3) \ + reg_entry libdbnum, offset, size ; +#define CONTENTS_LIBDB_REGF(libdbnum, offset, size, align, rsv1, name, index, \ + numentries, contentsize, regname_base, \ + regfile_name, rsv2, rsv3) \ + reg_entry libdbnum, offset, size ; + +/* A single table entry: */ + .macro reg_entry libdbnum, offset, size + .ifne (__last_offset-(__last_group_offset+\offset)) + /* padding entry */ + .word (0xFC000000+__last_offset-(__last_group_offset+\offset)) + .endif + .word \libdbnum /* actual entry */ + .set __last_offset, __last_group_offset+\offset+\size + .endm /* reg_entry */ + + +/* Table entry that marks the beginning of a group (coprocessor or "extra"): */ + .macro reg_group cpnum, num_entries, align + .set __last_group_offset, (__last_offset + \align- 1) & -\align + .ifne \num_entries + .word 0xFD000000+(\cpnum<<16)+\num_entries + .endif + .endm /* reg_group */ + +/* + * Register info tables. + */ + + .section .rodata, "a" + .globl _xtensa_reginfo_tables + .globl _xtensa_reginfo_table_size + .align 4 +_xtensa_reginfo_table_size: + .word _xtensa_reginfo_table_end - _xtensa_reginfo_tables + +_xtensa_reginfo_tables: + .set __last_offset, 0 + reg_group 0xFF, XCHAL_EXTRA_SA_CONTENTS_LIBDB_NUM, XCHAL_EXTRA_SA_ALIGN + XCHAL_EXTRA_SA_CONTENTS_LIBDB + reg_group 0, XCHAL_CP0_SA_CONTENTS_LIBDB_NUM, XCHAL_CP0_SA_ALIGN + XCHAL_CP0_SA_CONTENTS_LIBDB + reg_group 1, XCHAL_CP1_SA_CONTENTS_LIBDB_NUM, XCHAL_CP1_SA_ALIGN + XCHAL_CP1_SA_CONTENTS_LIBDB + reg_group 2, XCHAL_CP2_SA_CONTENTS_LIBDB_NUM, XCHAL_CP2_SA_ALIGN + XCHAL_CP2_SA_CONTENTS_LIBDB + reg_group 3, XCHAL_CP3_SA_CONTENTS_LIBDB_NUM, XCHAL_CP3_SA_ALIGN + XCHAL_CP3_SA_CONTENTS_LIBDB + reg_group 4, XCHAL_CP4_SA_CONTENTS_LIBDB_NUM, XCHAL_CP4_SA_ALIGN + XCHAL_CP4_SA_CONTENTS_LIBDB + reg_group 5, XCHAL_CP5_SA_CONTENTS_LIBDB_NUM, XCHAL_CP5_SA_ALIGN + XCHAL_CP5_SA_CONTENTS_LIBDB + reg_group 6, XCHAL_CP6_SA_CONTENTS_LIBDB_NUM, XCHAL_CP6_SA_ALIGN + XCHAL_CP6_SA_CONTENTS_LIBDB + reg_group 7, XCHAL_CP7_SA_CONTENTS_LIBDB_NUM, XCHAL_CP7_SA_ALIGN + XCHAL_CP7_SA_CONTENTS_LIBDB + .word 0xFC000000 /* invalid register number,marks end of table*/ +_xtensa_reginfo_table_end: + diff --git a/arch/xtensa/kernel/entry.S b/arch/xtensa/kernel/entry.S new file mode 100644 index 0000000000..c64a01f71d --- /dev/null +++ b/arch/xtensa/kernel/entry.S @@ -0,0 +1,1996 @@ +/* + * arch/xtensa/kernel/entry.S + * + * Low-level exception handling + * + * 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) 2004-2005 by Tensilica Inc. + * + * Chris Zankel + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Unimplemented features. */ + +#undef SIGNAL_HANDLING_IN_DOUBLE_EXCEPTION +#undef KERNEL_STACK_OVERFLOW_CHECK +#undef PREEMPTIBLE_KERNEL +#undef ALLOCA_EXCEPTION_IN_IRAM + +/* Not well tested. + * + * - fast_coprocessor + */ + +/* + * Macro to find first bit set in WINDOWBASE from the left + 1 + * + * 100....0 -> 1 + * 010....0 -> 2 + * 000....1 -> WSBITS + */ + + .macro ffs_ws bit mask + +#if XCHAL_HAVE_NSA + nsau \bit, \mask # 32-WSBITS ... 31 (32 iff 0) + addi \bit, \bit, WSBITS - 32 + 1 # uppest bit set -> return 1 +#else + movi \bit, WSBITS +#if WSBITS > 16 + _bltui \mask, 0x10000, 99f + addi \bit, \bit, -16 + extui \mask, \mask, 16, 16 +#endif +#if WSBITS > 8 +99: _bltui \mask, 0x100, 99f + addi \bit, \bit, -8 + srli \mask, \mask, 8 +#endif +99: _bltui \mask, 0x10, 99f + addi \bit, \bit, -4 + srli \mask, \mask, 4 +99: _bltui \mask, 0x4, 99f + addi \bit, \bit, -2 + srli \mask, \mask, 2 +99: _bltui \mask, 0x2, 99f + addi \bit, \bit, -1 +99: + +#endif + .endm + +/* ----------------- DEFAULT FIRST LEVEL EXCEPTION HANDLERS ----------------- */ + +/* + * First-level exception handler for user exceptions. + * Save some special registers, extra states and all registers in the AR + * register file that were in use in the user task, and jump to the common + * exception code. + * We save SAR (used to calculate WMASK), and WB and WS (we don't have to + * save them for kernel exceptions). + * + * Entry condition for user_exception: + * + * 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) + * excsave1: a3 + * + * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC + * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception + * + * Entry condition for _user_exception: + * + * a0-a3 and depc have been saved to PT_AREG0...PT_AREG3 and PT_DEPC + * excsave has been restored, and + * stack pointer (a1) has been set. + * + * Note: _user_exception might be at an odd adress. Don't use call0..call12 + */ + +ENTRY(user_exception) + + /* Save a2, a3, and depc, restore excsave_1 and set SP. */ + + xsr a3, EXCSAVE_1 + rsr a0, DEPC + s32i a1, a2, PT_AREG1 + s32i a0, a2, PT_AREG2 + s32i a3, a2, PT_AREG3 + mov a1, a2 + + .globl _user_exception +_user_exception: + + /* Save SAR and turn off single stepping */ + + movi a2, 0 + rsr a3, SAR + wsr a2, ICOUNTLEVEL + s32i a3, a1, PT_SAR + + /* Rotate ws so that the current windowbase is at bit0. */ + /* Assume ws = xxwww1yyyy. Rotate ws right, so that a2 = yyyyxxwww1 */ + + rsr a2, WINDOWBASE + rsr a3, WINDOWSTART + ssr a2 + s32i a2, a1, PT_WINDOWBASE + s32i a3, a1, PT_WINDOWSTART + slli a2, a3, 32-WSBITS + src a2, a3, a2 + srli a2, a2, 32-WSBITS + s32i a2, a1, PT_WMASK # needed for restoring registers + + /* Save only live registers. */ + + _bbsi.l a2, 1, 1f + s32i a4, a1, PT_AREG4 + s32i a5, a1, PT_AREG5 + s32i a6, a1, PT_AREG6 + s32i a7, a1, PT_AREG7 + _bbsi.l a2, 2, 1f + s32i a8, a1, PT_AREG8 + s32i a9, a1, PT_AREG9 + s32i a10, a1, PT_AREG10 + s32i a11, a1, PT_AREG11 + _bbsi.l a2, 3, 1f + s32i a12, a1, PT_AREG12 + s32i a13, a1, PT_AREG13 + s32i a14, a1, PT_AREG14 + s32i a15, a1, PT_AREG15 + _bnei a2, 1, 1f # only one valid frame? + + /* Only one valid frame, skip saving regs. */ + + j 2f + + /* Save the remaining registers. + * We have to save all registers up to the first '1' from + * the right, except the current frame (bit 0). + * Assume a2 is: 001001000110001 + * All regiser frames starting from the top fiel to the marked '1' + * must be saved. + */ + +1: addi a3, a2, -1 # eliminate '1' in bit 0: yyyyxxww0 + neg a3, a3 # yyyyxxww0 -> YYYYXXWW1+1 + and a3, a3, a2 # max. only one bit is set + + /* Find number of frames to save */ + + ffs_ws a0, a3 # number of frames to the '1' from left + + /* Store information into WMASK: + * bits 0..3: xxx1 masked lower 4 bits of the rotated windowstart, + * bits 4...: number of valid 4-register frames + */ + + slli a3, a0, 4 # number of frames to save in bits 8..4 + extui a2, a2, 0, 4 # mask for the first 16 registers + or a2, a3, a2 + s32i a2, a1, PT_WMASK # needed when we restore the reg-file + + /* Save 4 registers at a time */ + +1: rotw -1 + s32i a0, a5, PT_AREG_END - 16 + s32i a1, a5, PT_AREG_END - 12 + s32i a2, a5, PT_AREG_END - 8 + s32i a3, a5, PT_AREG_END - 4 + addi a0, a4, -1 + addi a1, a5, -16 + _bnez a0, 1b + + /* WINDOWBASE still in SAR! */ + + rsr a2, SAR # original WINDOWBASE + movi a3, 1 + ssl a2 + sll a3, a3 + wsr a3, WINDOWSTART # set corresponding WINDOWSTART bit + wsr a2, WINDOWBASE # and WINDOWSTART + rsync + + /* We are back to the original stack pointer (a1) */ + +2: +#if XCHAL_EXTRA_SA_SIZE + + /* For user exceptions, save the extra state into the user's TCB. + * Note: We must assume that xchal_extra_store_funcbody destroys a2..a15 + */ + + GET_CURRENT(a2,a1) + addi a2, a2, THREAD_CP_SAVE + xchal_extra_store_funcbody +#endif + + /* Now, jump to the common exception handler. */ + + j common_exception + + +/* + * First-level exit handler for kernel exceptions + * Save special registers and the live window frame. + * Note: Even though we changes the stack pointer, we don't have to do a + * MOVSP here, as we do that when we return from the exception. + * (See comment in the kernel exception exit code) + * + * Entry condition for kernel_exception: + * + * a0: trashed, original value saved on stack (PT_AREG0) + * a1: a1 + * a2: new stack pointer, original in DEPC + * a3: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + * + * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC + * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception + * + * Entry condition for _kernel_exception: + * + * a0-a3 and depc have been saved to PT_AREG0...PT_AREG3 and PT_DEPC + * excsave has been restored, and + * stack pointer (a1) has been set. + * + * Note: _kernel_exception might be at an odd adress. Don't use call0..call12 + */ + +ENTRY(kernel_exception) + + /* Save a0, a2, a3, DEPC and set SP. */ + + xsr a3, EXCSAVE_1 # restore a3, excsave_1 + rsr a0, DEPC # get a2 + s32i a1, a2, PT_AREG1 + s32i a0, a2, PT_AREG2 + s32i a3, a2, PT_AREG3 + mov a1, a2 + + .globl _kernel_exception +_kernel_exception: + + /* Save SAR and turn off single stepping */ + + movi a2, 0 + rsr a3, SAR + wsr a2, ICOUNTLEVEL + s32i a3, a1, PT_SAR + + /* Rotate ws so that the current windowbase is at bit0. */ + /* Assume ws = xxwww1yyyy. Rotate ws right, so that a2 = yyyyxxwww1 */ + + rsr a2, WINDOWBASE # don't need to save these, we only + rsr a3, WINDOWSTART # need shifted windowstart: windowmask + ssr a2 + slli a2, a3, 32-WSBITS + src a2, a3, a2 + srli a2, a2, 32-WSBITS + s32i a2, a1, PT_WMASK # needed for kernel_exception_exit + + /* Save only the live window-frame */ + + _bbsi.l a2, 1, 1f + s32i a4, a1, PT_AREG4 + s32i a5, a1, PT_AREG5 + s32i a6, a1, PT_AREG6 + s32i a7, a1, PT_AREG7 + _bbsi.l a2, 2, 1f + s32i a8, a1, PT_AREG8 + s32i a9, a1, PT_AREG9 + s32i a10, a1, PT_AREG10 + s32i a11, a1, PT_AREG11 + _bbsi.l a2, 3, 1f + s32i a12, a1, PT_AREG12 + s32i a13, a1, PT_AREG13 + s32i a14, a1, PT_AREG14 + s32i a15, a1, PT_AREG15 + +1: + +#ifdef KERNEL_STACK_OVERFLOW_CHECK + + /* Stack overflow check, for debugging */ + extui a2, a1, TASK_SIZE_BITS,XX + movi a3, SIZE?? + _bge a2, a3, out_of_stack_panic + +#endif + +/* + * This is the common exception handler. + * We get here from the user exception handler or simply by falling through + * from the kernel exception handler. + * Save the remaining special registers, switch to kernel mode, and jump + * to the second-level exception handler. + * + */ + +common_exception: + + /* Save EXCVADDR, DEBUGCAUSE, and PC, and clear LCOUNT */ + + rsr a2, DEBUGCAUSE + rsr a3, EPC_1 + s32i a2, a1, PT_DEBUGCAUSE + s32i a3, a1, PT_PC + + rsr a3, EXCVADDR + movi a2, 0 + s32i a3, a1, PT_EXCVADDR + xsr a2, LCOUNT + s32i a2, a1, PT_LCOUNT + + /* It is now save to restore the EXC_TABLE_FIXUP variable. */ + + rsr a0, EXCCAUSE + movi a3, 0 + rsr a2, EXCSAVE_1 + s32i a0, a1, PT_EXCCAUSE + s32i a3, a2, EXC_TABLE_FIXUP + + /* All unrecoverable states are saved on stack, now, and a1 is valid, + * so we can allow exceptions and interrupts (*) again. + * Set PS(EXCM = 0, UM = 0, RING = 0, OWB = 0, WOE = 1, INTLEVEL = X) + * + * (*) We only allow interrupts if PS.INTLEVEL was not set to 1 before + * (interrupts disabled) and if this exception is not an interrupt. + */ + + rsr a3, PS + addi a0, a0, -4 + movi a2, 1 + extui a3, a3, 0, 1 # a3 = PS.INTLEVEL[0] + moveqz a3, a2, a0 # a3 = 1 iff interrupt exception + movi a2, PS_WOE_MASK + or a3, a3, a2 + rsr a0, EXCCAUSE + xsr a3, PS + + s32i a3, a1, PT_PS # save ps + + /* Save LBEG, LEND */ + + rsr a2, LBEG + rsr a3, LEND + s32i a2, a1, PT_LBEG + s32i a3, a1, PT_LEND + + /* Go to second-level dispatcher. Set up parameters to pass to the + * exception handler and call the exception handler. + */ + + movi a4, exc_table + mov a6, a1 # pass stack frame + mov a7, a0 # pass EXCCAUSE + addx4 a4, a0, a4 + l32i a4, a4, EXC_TABLE_DEFAULT # load handler + + /* Call the second-level handler */ + + callx4 a4 + + /* Jump here for exception exit */ + +common_exception_return: + + /* Jump if we are returning from kernel exceptions. */ + +1: l32i a3, a1, PT_PS + _bbsi.l a3, PS_UM_SHIFT, 2f + j kernel_exception_exit + + /* Specific to a user exception exit: + * We need to check some flags for signal handling and rescheduling, + * and have to restore WB and WS, extra states, and all registers + * in the register file that were in use in the user task. + */ + +2: wsr a3, PS /* disable interrupts */ + + /* Check for signals (keep interrupts disabled while we read TI_FLAGS) + * Note: PS.INTLEVEL = 0, PS.EXCM = 1 + */ + + GET_THREAD_INFO(a2,a1) + l32i a4, a2, TI_FLAGS + + /* Enable interrupts again. + * Note: When we get here, we certainly have handled any interrupts. + * (Hint: There is only one user exception frame on stack) + */ + + movi a3, PS_WOE_MASK + + _bbsi.l a4, TIF_NEED_RESCHED, 3f + _bbci.l a4, TIF_SIGPENDING, 4f + +#ifndef SIGNAL_HANDLING_IN_DOUBLE_EXCEPTION + l32i a4, a1, PT_DEPC + bgeui a4, VALID_DOUBLE_EXCEPTION_ADDRESS, 4f +#endif + + /* Reenable interrupts and call do_signal() */ + + wsr a3, PS + movi a4, do_signal # int do_signal(struct pt_regs*, sigset_t*) + mov a6, a1 + movi a7, 0 + callx4 a4 + j 1b + +3: /* Reenable interrupts and reschedule */ + + wsr a3, PS + movi a4, schedule # void schedule (void) + callx4 a4 + j 1b + + /* Restore the state of the task and return from the exception. */ + + + /* If we are returning from a user exception, and the process + * to run next has PT_SINGLESTEP set, we want to setup + * ICOUNT and ICOUNTLEVEL to step one instruction. + * PT_SINGLESTEP is set by sys_ptrace (ptrace.c) + */ + +4: /* a2 holds GET_CURRENT(a2,a1) */ + + l32i a3, a2, TI_TASK + l32i a3, a3, TASK_PTRACE + bbci.l a3, PT_SINGLESTEP_BIT, 1f # jump if single-step flag is not set + + movi a3, -2 # PT_SINGLESTEP flag is set, + movi a4, 1 # icountlevel of 1 means it won't + wsr a3, ICOUNT # start counting until after rfe + wsr a4, ICOUNTLEVEL # so setup icount & icountlevel. + isync + +1: + +#if XCHAL_EXTRA_SA_SIZE + + /* For user exceptions, restore the extra state from the user's TCB. */ + + /* Note: a2 still contains GET_CURRENT(a2,a1) */ + addi a2, a2, THREAD_CP_SAVE + xchal_extra_load_funcbody + + /* We must assume that xchal_extra_store_funcbody destroys + * registers a2..a15. FIXME, this list can eventually be + * reduced once real register requirements of the macro are + * finalized. */ + +#endif /* XCHAL_EXTRA_SA_SIZE */ + + + /* Switch to the user thread WINDOWBASE. Save SP temporarily in DEPC */ + + l32i a2, a1, PT_WINDOWBASE + l32i a3, a1, PT_WINDOWSTART + wsr a1, DEPC # use DEPC as temp storage + wsr a3, WINDOWSTART # restore WINDOWSTART + ssr a2 # preserve user's WB in the SAR + wsr a2, WINDOWBASE # switch to user's saved WB + rsync + rsr a1, DEPC # restore stack pointer + l32i a2, a1, PT_WMASK # register frames saved (in bits 4...9) + rotw -1 # we restore a4..a7 + _bltui a6, 16, 1f # only have to restore current window? + + /* The working registers are a0 and a3. We are restoring to + * a4..a7. Be careful not to destroy what we have just restored. + * Note: wmask has the format YYYYM: + * Y: number of registers saved in groups of 4 + * M: 4 bit mask of first 16 registers + */ + + mov a2, a6 + mov a3, a5 + +2: rotw -1 # a0..a3 become a4..a7 + addi a3, a7, -4*4 # next iteration + addi a2, a6, -16 # decrementing Y in WMASK + l32i a4, a3, PT_AREG_END + 0 + l32i a5, a3, PT_AREG_END + 4 + l32i a6, a3, PT_AREG_END + 8 + l32i a7, a3, PT_AREG_END + 12 + _bgeui a2, 16, 2b + + /* Clear unrestored registers (don't leak anything to user-land */ + +1: rsr a0, WINDOWBASE + rsr a3, SAR + sub a3, a0, a3 + beqz a3, 2f + extui a3, a3, 0, WBBITS + +1: rotw -1 + addi a3, a7, -1 + movi a4, 0 + movi a5, 0 + movi a6, 0 + movi a7, 0 + bgei a3, 1, 1b + + /* We are back were we were when we started. + * Note: a2 still contains WMASK (if we've returned to the original + * frame where we had loaded a2), or at least the lower 4 bits + * (if we have restored WSBITS-1 frames). + */ + +2: j common_exception_exit + + /* This is the kernel exception exit. + * We avoided to do a MOVSP when we entered the exception, but we + * have to do it here. + */ + +kernel_exception_exit: + + /* Disable interrupts (a3 holds PT_PS) */ + + wsr a3, PS + +#ifdef PREEMPTIBLE_KERNEL + +#ifdef CONFIG_PREEMPT + + /* + * Note: We've just returned from a call4, so we have + * at least 4 addt'l regs. + */ + + /* Check current_thread_info->preempt_count */ + + GET_THREAD_INFO(a2) + l32i a3, a2, TI_PREEMPT + bnez a3, 1f + + l32i a2, a2, TI_FLAGS + +1: + +#endif + +#endif + + /* Check if we have to do a movsp. + * + * We only have to do a movsp if the previous window-frame has + * been spilled to the *temporary* exception stack instead of the + * task's stack. This is the case if the corresponding bit in + * WINDOWSTART for the previous window-frame was set before + * (not spilled) but is zero now (spilled). + * If this bit is zero, all other bits except the one for the + * current window frame are also zero. So, we can use a simple test: + * 'and' WINDOWSTART and WINDOWSTART-1: + * + * (XXXXXX1[0]* - 1) AND XXXXXX1[0]* = XXXXXX0[0]* + * + * The result is zero only if one bit was set. + * + * (Note: We might have gone through several task switches before + * we come back to the current task, so WINDOWBASE might be + * different from the time the exception occurred.) + */ + + /* Test WINDOWSTART before and after the exception. + * We actually have WMASK, so we only have to test if it is 1 or not. + */ + + l32i a2, a1, PT_WMASK + _beqi a2, 1, common_exception_exit # Spilled before exception,jump + + /* Test WINDOWSTART now. If spilled, do the movsp */ + + rsr a3, WINDOWSTART + addi a0, a3, -1 + and a3, a3, a0 + _bnez a3, common_exception_exit + + /* Do a movsp (we returned from a call4, so we have at least a0..a7) */ + + addi a0, a1, -16 + l32i a3, a0, 0 + l32i a4, a0, 4 + s32i a3, a1, PT_SIZE+0 + s32i a4, a1, PT_SIZE+4 + l32i a3, a0, 8 + l32i a4, a0, 12 + s32i a3, a1, PT_SIZE+8 + s32i a4, a1, PT_SIZE+12 + + /* Common exception exit. + * We restore the special register and the current window frame, and + * return from the exception. + * + * Note: We expect a2 to hold PT_WMASK + */ + +common_exception_exit: + + _bbsi.l a2, 1, 1f + l32i a4, a1, PT_AREG4 + l32i a5, a1, PT_AREG5 + l32i a6, a1, PT_AREG6 + l32i a7, a1, PT_AREG7 + _bbsi.l a2, 2, 1f + l32i a8, a1, PT_AREG8 + l32i a9, a1, PT_AREG9 + l32i a10, a1, PT_AREG10 + l32i a11, a1, PT_AREG11 + _bbsi.l a2, 3, 1f + l32i a12, a1, PT_AREG12 + l32i a13, a1, PT_AREG13 + l32i a14, a1, PT_AREG14 + l32i a15, a1, PT_AREG15 + + /* Restore PC, SAR */ + +1: l32i a2, a1, PT_PC + l32i a3, a1, PT_SAR + wsr a2, EPC_1 + wsr a3, SAR + + /* Restore LBEG, LEND, LCOUNT */ + + l32i a2, a1, PT_LBEG + l32i a3, a1, PT_LEND + wsr a2, LBEG + l32i a2, a1, PT_LCOUNT + wsr a3, LEND + wsr a2, LCOUNT + + /* Check if it was double exception. */ + + l32i a0, a1, PT_DEPC + l32i a3, a1, PT_AREG3 + l32i a2, a1, PT_AREG2 + _bgeui a0, VALID_DOUBLE_EXCEPTION_ADDRESS, 1f + + /* Restore a0...a3 and return */ + + l32i a0, a1, PT_AREG0 + l32i a1, a1, PT_AREG1 + rfe + +1: wsr a0, DEPC + l32i a0, a1, PT_AREG0 + l32i a1, a1, PT_AREG1 + rfde + +/* + * Debug exception handler. + * + * Currently, we don't support KGDB, so only user application can be debugged. + * + * When we get here, a0 is trashed and saved to excsave[debuglevel] + */ + +ENTRY(debug_exception) + + rsr a0, EPS + XCHAL_DEBUGLEVEL + bbsi.l a0, PS_EXCM_SHIFT, 1f # exception mode + + /* Set EPC_1 and EXCCAUSE */ + + wsr a2, DEPC # save a2 temporarily + rsr a2, EPC + XCHAL_DEBUGLEVEL + wsr a2, EPC_1 + + movi a2, EXCCAUSE_MAPPED_DEBUG + wsr a2, EXCCAUSE + + /* Restore PS to the value before the debug exc but with PS.EXCM set.*/ + + movi a2, 1 << PS_EXCM_SHIFT + or a2, a0, a2 + movi a0, debug_exception # restore a3, debug jump vector + wsr a2, PS + xsr a0, EXCSAVE + XCHAL_DEBUGLEVEL + + /* Switch to kernel/user stack, restore jump vector, and save a0 */ + + bbsi.l a2, PS_UM_SHIFT, 2f # jump if user mode + + addi a2, a1, -16-PT_SIZE # assume kernel stack + s32i a0, a2, PT_AREG0 + movi a0, 0 + s32i a1, a2, PT_AREG1 + s32i a0, a2, PT_DEPC # mark it as a regular exception + xsr a0, DEPC + s32i a3, a2, PT_AREG3 + s32i a0, a2, PT_AREG2 + mov a1, a2 + j _kernel_exception + +2: rsr a2, EXCSAVE_1 + l32i a2, a2, EXC_TABLE_KSTK # load kernel stack pointer + s32i a0, a2, PT_AREG0 + movi a0, 0 + s32i a1, a2, PT_AREG1 + s32i a0, a2, PT_DEPC + xsr a0, DEPC + s32i a3, a2, PT_AREG3 + s32i a0, a2, PT_AREG2 + mov a1, a2 + j _user_exception + + /* Debug exception while in exception mode. */ +1: j 1b // FIXME!! + + +/* + * We get here in case of an unrecoverable exception. + * The only thing we can do is to be nice and print a panic message. + * We only produce a single stack frame for panic, so ??? + * + * + * Entry conditions: + * + * - a0 contains the caller address; original value saved in excsave1. + * - the original a0 contains a valid return address (backtrace) or 0. + * - a2 contains a valid stackpointer + * + * Notes: + * + * - If the stack pointer could be invalid, the caller has to setup a + * dummy stack pointer (e.g. the stack of the init_task) + * + * - If the return address could be invalid, the caller has to set it + * to 0, so the backtrace would stop. + * + */ + .align 4 +unrecoverable_text: + .ascii "Unrecoverable error in exception handler\0" + +ENTRY(unrecoverable_exception) + + movi a0, 1 + movi a1, 0 + + wsr a0, WINDOWSTART + wsr a1, WINDOWBASE + rsync + + movi a1, PS_WOE_MASK | 1 + wsr a1, PS + rsync + + movi a1, init_task + movi a0, 0 + addi a1, a1, PT_REGS_OFFSET + + movi a4, panic + movi a6, unrecoverable_text + + callx4 a4 + +1: j 1b + + +/* -------------------------- FAST EXCEPTION HANDLERS ----------------------- */ + +/* + * Fast-handler for alloca exceptions + * + * The ALLOCA handler is entered when user code executes the MOVSP + * instruction and the caller's frame is not in the register file. + * In this case, the caller frame's a0..a3 are on the stack just + * below sp (a1), and this handler moves them. + * + * For "MOVSP ," without destination register a1, this routine + * simply moves the value from to without moving the save area. + * + * Entry condition: + * + * a0: trashed, original value saved on stack (PT_AREG0) + * a1: a1 + * a2: new stack pointer, original in DEPC + * a3: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + * + * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC + * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception + */ + +#if XCHAL_HAVE_BE +#define _EXTUI_MOVSP_SRC(ar) extui ar, ar, 4, 4 +#define _EXTUI_MOVSP_DST(ar) extui ar, ar, 0, 4 +#else +#define _EXTUI_MOVSP_SRC(ar) extui ar, ar, 0, 4 +#define _EXTUI_MOVSP_DST(ar) extui ar, ar, 4, 4 +#endif + +ENTRY(fast_alloca) + + /* We shouldn't be in a double exception. */ + + l32i a0, a2, PT_DEPC + _bgeui a0, VALID_DOUBLE_EXCEPTION_ADDRESS, .Lunhandled_double + + rsr a0, DEPC # get a2 + s32i a4, a2, PT_AREG4 # save a4 and + s32i a0, a2, PT_AREG2 # a2 to stack + + /* Exit critical section. */ + + movi a0, 0 + s32i a0, a3, EXC_TABLE_FIXUP + + /* Restore a3, excsave_1 */ + + xsr a3, EXCSAVE_1 # make sure excsave_1 is valid for dbl. + rsr a4, EPC_1 # get exception address + s32i a3, a2, PT_AREG3 # save a3 to stack + +#ifdef ALLOCA_EXCEPTION_IN_IRAM +#error iram not supported +#else + /* Note: l8ui not allowed in IRAM/IROM!! */ + l8ui a0, a4, 1 # read as(src) from MOVSP instruction +#endif + movi a3, .Lmovsp_src + _EXTUI_MOVSP_SRC(a0) # extract source register number + addx8 a3, a0, a3 + jx a3 + +.Lunhandled_double: + wsr a0, EXCSAVE_1 + movi a0, unrecoverable_exception + callx0 a0 + + .align 8 +.Lmovsp_src: + l32i a3, a2, PT_AREG0; _j 1f; .align 8 + mov a3, a1; _j 1f; .align 8 + 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 + mov a3, a5; _j 1f; .align 8 + mov a3, a6; _j 1f; .align 8 + mov a3, a7; _j 1f; .align 8 + mov a3, a8; _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 + +1: + +#ifdef ALLOCA_EXCEPTION_IN_IRAM +#error iram not supported +#else + l8ui a0, a4, 0 # read ar(dst) from MOVSP instruction +#endif + addi a4, a4, 3 # step over movsp + _EXTUI_MOVSP_DST(a0) # extract destination register + wsr a4, EPC_1 # save new epc_1 + + _bnei a0, 1, 1f # no 'movsp a1, ax': jump + + /* Move the save area. This implies the use of the L32E + * and S32E instructions, because this move must be done with + * the user's PS.RING privilege levels, not with ring 0 + * (kernel's) privileges currently active with PS.EXCM + * set. Note that we have stil registered a fixup routine with the + * double exception vector in case a double exception occurs. + */ + + /* a0,a4:avail a1:old user stack a2:exc. stack a3:new user stack. */ + + l32e a0, a1, -16 + l32e a4, a1, -12 + s32e a0, a3, -16 + s32e a4, a3, -12 + l32e a0, a1, -8 + l32e a4, a1, -4 + s32e a0, a3, -8 + s32e a4, a3, -4 + + /* Restore stack-pointer and all the other saved registers. */ + + mov a1, a3 + + l32i a4, a2, PT_AREG4 + l32i a3, a2, PT_AREG3 + l32i a0, a2, PT_AREG0 + l32i a2, a2, PT_AREG2 + rfe + + /* MOVSP , was invoked with != a1. + * Because the stack pointer is not being modified, + * we should be able to just modify the pointer + * without moving any save area. + * The processor only traps these occurrences if the + * caller window isn't live, so unfortunately we can't + * use this as an alternate trap mechanism. + * So we just do the move. This requires that we + * resolve the destination register, not just the source, + * so there's some extra work. + * (PERHAPS NOT REALLY NEEDED, BUT CLEANER...) + */ + + /* a0 dst-reg, a1 user-stack, a2 stack, a3 value of src reg. */ + +1: movi a4, .Lmovsp_dst + addx8 a4, a0, a4 + jx a4 + + .align 8 +.Lmovsp_dst: + s32i a3, a2, PT_AREG0; _j 1f; .align 8 + mov a1, a3; _j 1f; .align 8 + s32i a3, a2, PT_AREG2; _j 1f; .align 8 + s32i a3, a2, PT_AREG3; _j 1f; .align 8 + s32i a3, a2, PT_AREG4; _j 1f; .align 8 + mov a5, a3; _j 1f; .align 8 + mov a6, a3; _j 1f; .align 8 + mov a7, a3; _j 1f; .align 8 + mov a8, a3; _j 1f; .align 8 + mov a9, a3; _j 1f; .align 8 + mov a10, a3; _j 1f; .align 8 + mov a11, a3; _j 1f; .align 8 + mov a12, a3; _j 1f; .align 8 + mov a13, a3; _j 1f; .align 8 + mov a14, a3; _j 1f; .align 8 + mov a15, a3; _j 1f; .align 8 + +1: l32i a4, a2, PT_AREG4 + l32i a3, a2, PT_AREG3 + l32i a0, a2, PT_AREG0 + l32i a2, a2, PT_AREG2 + rfe + + +/* + * fast system calls. + * + * WARNING: The kernel doesn't save the entire user context before + * handling a fast system call. These functions are small and short, + * usually offering some functionality not available to user tasks. + * + * BE CAREFUL TO PRESERVE THE USER'S CONTEXT. + * + * Entry condition: + * + * a0: trashed, original value saved on stack (PT_AREG0) + * a1: a1 + * a2: new stack pointer, original in DEPC + * a3: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + */ + +ENTRY(fast_syscall_kernel) + + /* Skip syscall. */ + + rsr a0, EPC_1 + addi a0, a0, 3 + wsr a0, EPC_1 + + l32i a0, a2, PT_DEPC + bgeui a0, VALID_DOUBLE_EXCEPTION_ADDRESS, fast_syscall_unrecoverable + + rsr a0, DEPC # get syscall-nr + _beqz a0, fast_syscall_spill_registers + + addi a0, a0, -__NR_sysxtensa + _beqz a0, fast_syscall_sysxtensa + + j kernel_exception + + +ENTRY(fast_syscall_user) + + /* Skip syscall. */ + + rsr a0, EPC_1 + addi a0, a0, 3 + wsr a0, EPC_1 + + l32i a0, a2, PT_DEPC + bgeui a0, VALID_DOUBLE_EXCEPTION_ADDRESS, fast_syscall_unrecoverable + + rsr a0, DEPC # get syscall-nr + _beqz a0, fast_syscall_spill_registers + + addi a0, a0, -__NR_sysxtensa + _beqz a0, fast_syscall_sysxtensa + + j user_exception + +ENTRY(fast_syscall_unrecoverable) + + /* Restore all states. */ + + l32i a0, a2, PT_AREG0 # restore a0 + xsr a2, DEPC # restore a2, depc + rsr a3, EXCSAVE_1 + + wsr a0, EXCSAVE_1 + movi a0, unrecoverable_exception + callx0 a0 + + + +/* + * sysxtensa syscall handler + * + * int sysxtensa (XTENSA_ATOMIC_SET, ptr, val, unused); + * int sysxtensa (XTENSA_ATOMIC_ADD, ptr, val, unused); + * int sysxtensa (XTENSA_ATOMIC_EXG_ADD, ptr, val, unused); + * int sysxtensa (XTENSA_ATOMIC_CMP_SWP, ptr, oldval, newval); + * a2 a6 a3 a4 a5 + * + * Entry condition: + * + * a0: trashed, original value saved on stack (PT_AREG0) + * a1: a1 + * a2: new stack pointer, original in DEPC + * a3: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + * + * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC + * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception + * + * Note: we don't have to save a2; a2 holds the return value + * + * We use the two macros TRY and CATCH: + * + * TRY adds an entry to the __ex_table fixup table for the immediately + * following instruction. + * + * CATCH catches any exception that occurred at one of the preceeding TRY + * statements and continues from there + * + * Usage TRY l32i a0, a1, 0 + * + * done: rfe + * CATCH + * j done + */ + +#define TRY \ + .section __ex_table, "a"; \ + .word 66f, 67f; \ + .text; \ +66: + +#define CATCH \ +67: + +ENTRY(fast_syscall_sysxtensa) + + _beqz a6, 1f + _blti a6, SYSXTENSA_COUNT, 2f + +1: j user_exception + +2: xsr a3, EXCSAVE_1 # restore a3, excsave1 + s32i a7, a2, PT_AREG7 + + movi a7, 4 # sizeof(unsigned int) + verify_area a3, a7, a0, a2, .Leac + + _beqi a6, SYSXTENSA_ATOMIC_SET, .Lset + _beqi a6, SYSXTENSA_ATOMIC_EXG_ADD, .Lexg + _beqi a6, SYSXTENSA_ATOMIC_ADD, .Ladd + + /* Fall through for SYSXTENSA_ATOMIC_CMP_SWP */ + +.Lswp: /* Atomic compare and swap */ + +TRY l32i a7, a3, 0 # read old value + bne a7, a4, 1f # same as old value? jump + s32i a5, a3, 0 # different, modify value + movi a7, 1 # and return 1 + j .Lret + +1: movi a7, 0 # same values: return 0 + j .Lret + +.Ladd: /* Atomic add */ +.Lexg: /* Atomic (exchange) add */ + +TRY l32i a7, a3, 0 + add a4, a4, a7 + s32i a4, a3, 0 + j .Lret + +.Lset: /* Atomic set */ + +TRY l32i a7, a3, 0 # read old value as return value + s32i a4, a3, 0 # write new value + +.Lret: mov a0, a2 + mov a2, a7 + l32i a7, a0, PT_AREG7 + l32i a3, a0, PT_AREG3 + l32i a0, a0, PT_AREG0 + rfe + +CATCH +.Leac: movi a7, -EFAULT + j .Lret + + + +/* fast_syscall_spill_registers. + * + * Entry condition: + * + * a0: trashed, original value saved on stack (PT_AREG0) + * a1: a1 + * a2: new stack pointer, original in DEPC + * a3: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + * + * Note: We assume the stack pointer is EXC_TABLE_KSTK in the fixup handler. + * Note: We don't need to save a2 in depc (return value) + */ + +ENTRY(fast_syscall_spill_registers) + + /* Register a FIXUP handler (pass current wb as a parameter) */ + + movi a0, fast_syscall_spill_registers_fixup + s32i a0, a3, EXC_TABLE_FIXUP + rsr a0, WINDOWBASE + s32i a0, a3, EXC_TABLE_PARAM + + /* Save a3 and SAR on stack. */ + + rsr a0, SAR + xsr a3, EXCSAVE_1 # restore a3 and excsave_1 + s32i a0, a2, PT_AREG4 # store SAR to PT_AREG4 + s32i a3, a2, PT_AREG3 + + /* The spill routine might clobber a7, a11, and a15. */ + + s32i a7, a2, PT_AREG5 + s32i a11, a2, PT_AREG6 + s32i a15, a2, PT_AREG7 + + call0 _spill_registers # destroys a3, DEPC, and SAR + + /* Advance PC, restore registers and SAR, and return from exception. */ + + l32i a3, a2, PT_AREG4 + l32i a0, a2, PT_AREG0 + wsr a3, SAR + l32i a3, a2, PT_AREG3 + + /* Restore clobbered registers. */ + + l32i a7, a2, PT_AREG5 + l32i a11, a2, PT_AREG6 + l32i a15, a2, PT_AREG7 + + movi a2, 0 + rfe + +/* Fixup handler. + * + * We get here if the spill routine causes an exception, e.g. tlb miss. + * We basically restore WINDOWBASE and WINDOWSTART to the condition when + * we entered the spill routine and jump to the user exception handler. + * + * a0: value of depc, original value in depc + * a2: trashed, original value in EXC_TABLE_DOUBLE_SAVE + * a3: exctable, original value in excsave1 + */ + +fast_syscall_spill_registers_fixup: + + rsr a2, WINDOWBASE # get current windowbase (a2 is saved) + xsr a0, DEPC # restore depc and a0 + ssl a2 # set shift (32 - WB) + + /* We need to make sure the current registers (a0-a3) are preserved. + * To do this, we simply set the bit for the current window frame + * in WS, so that the exception handlers save them to the task stack. + */ + + rsr a3, EXCSAVE_1 # get spill-mask + slli a2, a3, 1 # shift left by one + + slli a3, a2, 32-WSBITS + src a2, a2, a3 # a1 = xxwww1yyxxxwww1yy...... + wsr a2, WINDOWSTART # set corrected windowstart + + movi a3, exc_table + l32i a2, a3, EXC_TABLE_DOUBLE_SAVE # restore a2 + l32i a3, a3, EXC_TABLE_PARAM # original WB (in user task) + + /* Return to the original (user task) WINDOWBASE. + * We leave the following frame behind: + * a0, a1, a2 same + * a3: trashed (saved in excsave_1) + * depc: depc (we have to return to that address) + * excsave_1: a3 + */ + + wsr a3, WINDOWBASE + rsync + + /* We are now in the original frame when we entered _spill_registers: + * a0: return address + * a1: used, stack pointer + * a2: kernel stack pointer + * a3: available, saved in EXCSAVE_1 + * depc: exception address + * excsave: a3 + * Note: This frame might be the same as above. + */ + +#ifdef SIGNAL_HANDLING_IN_DOUBLE_EXCEPTION + /* Restore registers we precautiously saved. + * We have the value of the 'right' a3 + */ + + l32i a7, a2, PT_AREG5 + l32i a11, a2, PT_AREG6 + l32i a15, a2, PT_AREG7 +#endif + + /* Setup stack pointer. */ + + addi a2, a2, -PT_USER_SIZE + s32i a0, a2, PT_AREG0 + + /* Make sure we return to this fixup handler. */ + + movi a3, fast_syscall_spill_registers_fixup_return + s32i a3, a2, PT_DEPC # setup depc + + /* Jump to the exception handler. */ + + movi a3, exc_table + rsr a0, EXCCAUSE + addx4 a0, a0, a3 # find entry in table + l32i a0, a0, EXC_TABLE_FAST_USER # load handler + jx a0 + +fast_syscall_spill_registers_fixup_return: + + /* When we return here, all registers have been restored (a2: DEPC) */ + + wsr a2, DEPC # exception address + + /* Restore fixup handler. */ + + xsr a3, EXCSAVE_1 + movi a2, fast_syscall_spill_registers_fixup + s32i a2, a3, EXC_TABLE_FIXUP + rsr a2, WINDOWBASE + s32i a2, a3, EXC_TABLE_PARAM + l32i a2, a3, EXC_TABLE_KSTK + +#ifdef SIGNAL_HANDLING_IN_DOUBLE_EXCEPTION + /* Save registers again that might be clobbered. */ + + s32i a7, a2, PT_AREG5 + s32i a11, a2, PT_AREG6 + s32i a15, a2, PT_AREG7 +#endif + + /* Load WB at the time the exception occurred. */ + + rsr a3, SAR # WB is still in SAR + neg a3, a3 + wsr a3, WINDOWBASE + rsync + + /* Restore a3 and return. */ + + movi a3, exc_table + xsr a3, EXCSAVE_1 + + rfde + + +/* + * spill all registers. + * + * This is not a real function. The following conditions must be met: + * + * - must be called with call0. + * - uses DEPC, a3 and SAR. + * - the last 'valid' register of each frame are clobbered. + * - the caller must have registered a fixup handler + * (or be inside a critical section) + * - PS_EXCM must be set (PS_WOE cleared?) + */ + +ENTRY(_spill_registers) + + /* + * Rotate ws so that the current windowbase is at bit 0. + * Assume ws = xxxwww1yy (www1 current window frame). + * Rotate ws right so that a2 = yyxxxwww1. + */ + + wsr a2, DEPC # preserve a2 + rsr a2, WINDOWBASE + rsr a3, WINDOWSTART + ssr a2 # holds WB + slli a2, a3, WSBITS + or a3, a3, a2 # a2 = xxxwww1yyxxxwww1yy + srl a3, a3 + + /* We are done if there are no more than the current register frame. */ + + extui a3, a3, 1, WSBITS-2 # a3 = 0yyxxxwww + movi a2, (1 << (WSBITS-1)) + _beqz a3, .Lnospill # only one active frame? jump + + /* We want 1 at the top, so that we return to the current windowbase */ + + or a3, a3, a2 # 1yyxxxwww + + /* Skip empty frames - get 'oldest' WINDOWSTART-bit. */ + + wsr a3, WINDOWSTART # save shifted windowstart + neg a2, a3 + and a3, a2, a3 # first bit set from right: 000010000 + + ffs_ws a2, a3 # a2: shifts to skip empty frames + movi a3, WSBITS + sub a2, a3, a2 # WSBITS-a2:number of 0-bits from right + ssr a2 # save in SAR for later. + + rsr a3, WINDOWBASE + add a3, a3, a2 + rsr a2, DEPC # restore a2 + wsr a3, WINDOWBASE + rsync + + rsr a3, WINDOWSTART + srl a3, a3 # shift windowstart + + /* WB is now just one frame below the oldest frame in the register + window. WS is shifted so the oldest frame is in bit 0, thus, WB + and WS differ by one 4-register frame. */ + + /* Save frames. Depending what call was used (call4, call8, call12), + * we have to save 4,8. or 12 registers. + */ + + _bbsi.l a3, 1, .Lc4 + _bbsi.l a3, 2, .Lc8 + + /* Special case: we have a call12-frame starting at a4. */ + + _bbci.l a3, 3, .Lc12 # bit 3 shouldn't be zero! (Jump to Lc12 first) + + s32e a4, a1, -16 # a1 is valid with an empty spill area + l32e a4, a5, -12 + s32e a8, a4, -48 + mov a8, a4 + l32e a4, a1, -16 + j .Lc12c + +.Lloop: _bbsi.l a3, 1, .Lc4 + _bbci.l a3, 2, .Lc12 + +.Lc8: s32e a4, a13, -16 + l32e a4, a5, -12 + s32e a8, a4, -32 + s32e a5, a13, -12 + s32e a6, a13, -8 + s32e a7, a13, -4 + s32e a9, a4, -28 + s32e a10, a4, -24 + s32e a11, a4, -20 + + srli a11, a3, 2 # shift windowbase by 2 + rotw 2 + _bnei a3, 1, .Lloop + +.Lexit: /* Done. Do the final rotation, set WS, and return. */ + + rotw 1 + rsr a3, WINDOWBASE + ssl a3 + movi a3, 1 + sll a3, a3 + wsr a3, WINDOWSTART + +.Lnospill: + jx a0 + +.Lc4: s32e a4, a9, -16 + s32e a5, a9, -12 + s32e a6, a9, -8 + s32e a7, a9, -4 + + srli a7, a3, 1 + rotw 1 + _bnei a3, 1, .Lloop + j .Lexit + +.Lc12: _bbci.l a3, 3, .Linvalid_mask # bit 2 shouldn't be zero! + + /* 12-register frame (call12) */ + + l32e a2, a5, -12 + s32e a8, a2, -48 + mov a8, a2 + +.Lc12c: s32e a9, a8, -44 + s32e a10, a8, -40 + s32e a11, a8, -36 + s32e a12, a8, -32 + s32e a13, a8, -28 + s32e a14, a8, -24 + s32e a15, a8, -20 + srli a15, a3, 3 + + /* The stack pointer for a4..a7 is out of reach, so we rotate the + * window, grab the stackpointer, and rotate back. + * Alternatively, we could also use the following approach, but that + * makes the fixup routine much more complicated: + * rotw 1 + * s32e a0, a13, -16 + * ... + * rotw 2 + */ + + rotw 1 + mov a5, a13 + rotw -1 + + s32e a4, a9, -16 + s32e a5, a9, -12 + s32e a6, a9, -8 + s32e a7, a9, -4 + + rotw 3 + + _beqi a3, 1, .Lexit + j .Lloop + +.Linvalid_mask: + + /* We get here because of an unrecoverable error in the window + * registers. If we are in user space, we kill the application, + * however, this condition is unrecoverable in kernel space. + */ + + rsr a0, PS + _bbci.l a0, PS_UM_SHIFT, 1f + + /* User space: Setup a dummy frame and kill application. + * Note: We assume EXC_TABLE_KSTK contains a valid stack pointer. + */ + + movi a0, 1 + movi a1, 0 + + wsr a0, WINDOWSTART + wsr a1, WINDOWBASE + rsync + + movi a0, 0 + + movi a3, exc_table + l32i a1, a3, EXC_TABLE_KSTK + wsr a3, EXCSAVE_1 + + movi a4, PS_WOE_MASK | 1 + wsr a4, PS + rsync + + movi a6, SIGSEGV + movi a4, do_exit + callx4 a4 + +1: /* Kernel space: PANIC! */ + + wsr a0, EXCSAVE_1 + movi a0, unrecoverable_exception + callx0 a0 # should not return +1: j 1b + +/* + * We should never get here. Bail out! + */ + +ENTRY(fast_second_level_miss_double_kernel) + +1: movi a0, unrecoverable_exception + callx0 a0 # should not return +1: j 1b + +/* First-level entry handler for user, kernel, and double 2nd-level + * TLB miss exceptions. Note that for now, user and kernel miss + * exceptions share the same entry point and are handled identically. + * + * An old, less-efficient C version of this function used to exist. + * We include it below, interleaved as comments, for reference. + * + * Entry condition: + * + * a0: trashed, original value saved on stack (PT_AREG0) + * a1: a1 + * a2: new stack pointer, original in DEPC + * a3: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + * + * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC + * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception + */ + +ENTRY(fast_second_level_miss) + + /* Save a1. Note: we don't expect a double exception. */ + + s32i a1, a2, PT_AREG1 + + /* We need to map the page of PTEs for the user task. Find + * the pointer to that page. Also, it's possible for tsk->mm + * to be NULL while tsk->active_mm is nonzero if we faulted on + * a vmalloc address. In that rare case, we must use + * active_mm instead to avoid a fault in this handler. See + * + * http://mail.nl.linux.org/linux-mm/2002-08/msg00258.html + * (or search Internet on "mm vs. active_mm") + * + * if (!mm) + * mm = tsk->active_mm; + * pgd = pgd_offset (mm, regs->excvaddr); + * pmd = pmd_offset (pgd, regs->excvaddr); + * pmdval = *pmd; + */ + + GET_CURRENT(a1,a2) + l32i a0, a1, TASK_MM # tsk->mm + beqz a0, 9f + +8: rsr a1, EXCVADDR # fault address + _PGD_OFFSET(a0, a1, a1) + l32i a0, a0, 0 # read pmdval + //beqi a0, _PAGE_USER, 2f + beqz a0, 2f + + /* Read ptevaddr and convert to top of page-table page. + * + * vpnval = read_ptevaddr_register() & PAGE_MASK; + * vpnval += DTLB_WAY_PGTABLE; + * pteval = mk_pte (virt_to_page(pmd_val(pmdval)), PAGE_KERNEL); + * write_dtlb_entry (pteval, vpnval); + * + * The messy computation for 'pteval' above really simplifies + * into the following: + * + * pteval = ((pmdval - PAGE_OFFSET) & PAGE_MASK) | PAGE_KERNEL + */ + + movi a1, -PAGE_OFFSET + add a0, a0, a1 # pmdval - PAGE_OFFSET + extui a1, a0, 0, PAGE_SHIFT # ... & PAGE_MASK + xor a0, a0, a1 + + + movi a1, PAGE_DIRECTORY + or a0, a0, a1 # ... | PAGE_DIRECTORY + + rsr a1, PTEVADDR + srli a1, a1, PAGE_SHIFT + slli a1, a1, PAGE_SHIFT # ptevaddr & PAGE_MASK + addi a1, a1, DTLB_WAY_PGTABLE # ... + way_number + + wdtlb a0, a1 + dsync + + /* Exit critical section. */ + + movi a0, 0 + s32i a0, a3, EXC_TABLE_FIXUP + + /* Restore the working registers, and return. */ + + l32i a0, a2, PT_AREG0 + l32i a1, a2, PT_AREG1 + l32i a2, a2, PT_DEPC + xsr a3, EXCSAVE_1 + + bgeui a2, VALID_DOUBLE_EXCEPTION_ADDRESS, 1f + + /* Restore excsave1 and return. */ + + rsr a2, DEPC + rfe + + /* Return from double exception. */ + +1: xsr a2, DEPC + esync + rfde + +9: l32i a0, a1, TASK_ACTIVE_MM # unlikely case mm == 0 + j 8b + +2: /* Invalid PGD, default exception handling */ + + rsr a1, DEPC + xsr a3, EXCSAVE_1 + s32i a1, a2, PT_AREG2 + s32i a3, a2, PT_AREG3 + mov a1, a2 + + rsr a2, PS + bbsi.l a2, PS_UM_SHIFT, 1f + j _kernel_exception +1: j _user_exception + + +/* + * StoreProhibitedException + * + * Update the pte and invalidate the itlb mapping for this pte. + * + * Entry condition: + * + * a0: trashed, original value saved on stack (PT_AREG0) + * a1: a1 + * a2: new stack pointer, original in DEPC + * a3: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + * + * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC + * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception + */ + +ENTRY(fast_store_prohibited) + + /* Save a1 and a4. */ + + s32i a1, a2, PT_AREG1 + s32i a4, a2, PT_AREG4 + + GET_CURRENT(a1,a2) + l32i a0, a1, TASK_MM # tsk->mm + beqz a0, 9f + +8: rsr a1, EXCVADDR # fault address + _PGD_OFFSET(a0, a1, a4) + l32i a0, a0, 0 + //beqi a0, _PAGE_USER, 2f # FIXME use _PAGE_INVALID + beqz a0, 2f + + _PTE_OFFSET(a0, a1, a4) + l32i a4, a0, 0 # read pteval + movi a1, _PAGE_VALID | _PAGE_RW + bnall a4, a1, 2f + + movi a1, _PAGE_ACCESSED | _PAGE_DIRTY | _PAGE_WRENABLE + or a4, a4, a1 + rsr a1, EXCVADDR + s32i a4, a0, 0 + + /* We need to flush the cache if we have page coloring. */ +#if (DCACHE_WAY_SIZE > PAGE_SIZE) && XCHAL_DCACHE_IS_WRITEBACK + dhwb a0, 0 +#endif + pdtlb a0, a1 + beqz a0, 1f + idtlb a0 // FIXME do we need this? + wdtlb a4, a0 +1: + + /* Exit critical section. */ + + movi a0, 0 + s32i a0, a3, EXC_TABLE_FIXUP + + /* Restore the working registers, and return. */ + + l32i a4, a2, PT_AREG4 + l32i a1, a2, PT_AREG1 + l32i a0, a2, PT_AREG0 + l32i a2, a2, PT_DEPC + + /* Restore excsave1 and a3. */ + + xsr a3, EXCSAVE_1 + bgeui a2, VALID_DOUBLE_EXCEPTION_ADDRESS, 1f + + rsr a2, DEPC + rfe + + /* Double exception. Restore FIXUP handler and return. */ + +1: xsr a2, DEPC + esync + rfde + +9: l32i a0, a1, TASK_ACTIVE_MM # unlikely case mm == 0 + j 8b + +2: /* If there was a problem, handle fault in C */ + + rsr a4, DEPC # still holds a2 + xsr a3, EXCSAVE_1 + s32i a4, a2, PT_AREG2 + s32i a3, a2, PT_AREG3 + l32i a4, a2, PT_AREG4 + mov a1, a2 + + rsr a2, PS + bbsi.l a2, PS_UM_SHIFT, 1f + j _kernel_exception +1: j _user_exception + + +#if XCHAL_EXTRA_SA_SIZE + +#warning fast_coprocessor untested + +/* + * Entry condition: + * + * a0: trashed, original value saved on stack (PT_AREG0) + * a1: a1 + * a2: new stack pointer, original in DEPC + * a3: dispatch table + * depc: a2, original value saved on stack (PT_DEPC) + * excsave_1: a3 + * + * PT_DEPC >= VALID_DOUBLE_EXCEPTION_ADDRESS: double exception, DEPC + * < VALID_DOUBLE_EXCEPTION_ADDRESS: regular exception + */ + +ENTRY(fast_coprocessor_double) + wsr a0, EXCSAVE_1 + movi a0, unrecoverable_exception + callx0 a0 + +ENTRY(fast_coprocessor) + + /* Fatal if we are in a double exception. */ + + l32i a0, a2, PT_DEPC + _bgeui a0, VALID_DOUBLE_EXCEPTION_ADDRESS, fast_coprocessor_double + + /* Save some registers a1, a3, a4, SAR */ + + xsr a3, EXCSAVE_1 + s32i a3, a2, PT_AREG3 + rsr a3, SAR + s32i a4, a2, PT_AREG4 + s32i a1, a2, PT_AREG1 + s32i a5, a1, PT_AREG5 + s32i a3, a2, PT_SAR + mov a1, a2 + + /* Currently, the HAL macros only guarantee saving a0 and a1. + * These can and will be refined in the future, but for now, + * just save the remaining registers of a2...a15. + */ + s32i a6, a1, PT_AREG6 + s32i a7, a1, PT_AREG7 + s32i a8, a1, PT_AREG8 + s32i a9, a1, PT_AREG9 + s32i a10, a1, PT_AREG10 + s32i a11, a1, PT_AREG11 + s32i a12, a1, PT_AREG12 + s32i a13, a1, PT_AREG13 + s32i a14, a1, PT_AREG14 + s32i a15, a1, PT_AREG15 + + /* Find coprocessor number. Subtract first CP EXCCAUSE from EXCCAUSE */ + + rsr a0, EXCCAUSE + addi a3, a0, -XCHAL_EXCCAUSE_COPROCESSOR0_DISABLED + + /* Set corresponding CPENABLE bit */ + + movi a4, 1 + ssl a3 # SAR: 32 - coprocessor_number + rsr a5, CPENABLE + sll a4, a4 + or a4, a5, a4 + wsr a4, CPENABLE + rsync + movi a5, coprocessor_info # list of owner and offset into cp_save + addx8 a0, a4, a5 # entry for CP + + bne a4, a5, .Lload # bit wasn't set before, cp not in use + + /* Now compare the current task with the owner of the coprocessor. + * If they are the same, there is no reason to save or restore any + * coprocessor state. Having already enabled the coprocessor, + * branch ahead to return. + */ + GET_CURRENT(a5,a1) + l32i a4, a0, COPROCESSOR_INFO_OWNER # a4: current owner for this CP + beq a4, a5, .Ldone + + /* Find location to dump current coprocessor state: + * task_struct->task_cp_save_offset + coprocessor_offset[coprocessor] + * + * Note: a0 pointer to the entry in the coprocessor owner table, + * a3 coprocessor number, + * a4 current owner of coprocessor. + */ + l32i a5, a0, COPROCESSOR_INFO_OFFSET + addi a2, a4, THREAD_CP_SAVE + add a2, a2, a5 + + /* Store current coprocessor states. (a5 still has CP number) */ + + xchal_cpi_store_funcbody + + /* The macro might have destroyed a3 (coprocessor number), but + * SAR still has 32 - coprocessor_number! + */ + movi a3, 32 + rsr a4, SAR + sub a3, a3, a4 + +.Lload: /* A new task now owns the corpocessors. Save its TCB pointer into + * the coprocessor owner table. + * + * Note: a0 pointer to the entry in the coprocessor owner table, + * a3 coprocessor number. + */ + GET_CURRENT(a4,a1) + s32i a4, a0, 0 + + /* Find location from where to restore the current coprocessor state.*/ + + l32i a5, a0, COPROCESSOR_INFO_OFFSET + addi a2, a4, THREAD_CP_SAVE + add a2, a2, a4 + + xchal_cpi_load_funcbody + + /* We must assume that the xchal_cpi_store_funcbody macro destroyed + * registers a2..a15. + */ + +.Ldone: l32i a15, a1, PT_AREG15 + l32i a14, a1, PT_AREG14 + l32i a13, a1, PT_AREG13 + l32i a12, a1, PT_AREG12 + l32i a11, a1, PT_AREG11 + l32i a10, a1, PT_AREG10 + l32i a9, a1, PT_AREG9 + l32i a8, a1, PT_AREG8 + l32i a7, a1, PT_AREG7 + l32i a6, a1, PT_AREG6 + l32i a5, a1, PT_AREG5 + l32i a4, a1, PT_AREG4 + l32i a3, a1, PT_AREG3 + l32i a2, a1, PT_AREG2 + l32i a0, a1, PT_AREG0 + l32i a1, a1, PT_AREG1 + + rfe + +#endif /* XCHAL_EXTRA_SA_SIZE */ + +/* + * Task switch. + * + * struct task* _switch_to (struct task* prev, struct task* next) + * a2 a2 a3 + */ + +ENTRY(_switch_to) + + entry a1, 16 + + mov a4, a3 # preserve a3 + + s32i a0, a2, THREAD_RA # save return address + s32i a1, a2, THREAD_SP # save stack pointer + + /* Disable ints while we manipulate the stack pointer; spill regs. */ + + movi a5, PS_EXCM_MASK | LOCKLEVEL + xsr a5, PS + rsr a3, EXCSAVE_1 + rsync + s32i a3, a3, EXC_TABLE_FIXUP /* enter critical section */ + + call0 _spill_registers + + /* Set kernel stack (and leave critical section) + * Note: It's save to set it here. The stack will not be overwritten + * because the kernel stack will only be loaded again after + * we return from kernel space. + */ + + l32i a0, a4, TASK_THREAD_INFO + rsr a3, EXCSAVE_1 # exc_table + movi a1, 0 + addi a0, a0, PT_REGS_OFFSET + s32i a1, a3, EXC_TABLE_FIXUP + s32i a0, a3, EXC_TABLE_KSTK + + /* restore context of the task that 'next' addresses */ + + l32i a0, a4, THREAD_RA /* restore return address */ + l32i a1, a4, THREAD_SP /* restore stack pointer */ + + wsr a5, PS + rsync + + retw + + +ENTRY(ret_from_fork) + + /* void schedule_tail (struct task_struct *prev) + * Note: prev is still in a6 (return value from fake call4 frame) + */ + movi a4, schedule_tail + callx4 a4 + + movi a4, do_syscall_trace + callx4 a4 + + j common_exception_return + + + +/* + * Table of syscalls + */ + +.data +.align 4 +.global sys_call_table +sys_call_table: + +#define SYSCALL(call, narg) .word call +#include "syscalls.h" + +/* + * Number of arguments of each syscall + */ + +.global sys_narg_table +sys_narg_table: + +#undef SYSCALL +#define SYSCALL(call, narg) .byte narg +#include "syscalls.h" + diff --git a/arch/xtensa/kernel/head.S b/arch/xtensa/kernel/head.S new file mode 100644 index 0000000000..6e9b5225b8 --- /dev/null +++ b/arch/xtensa/kernel/head.S @@ -0,0 +1,237 @@ +/* + * 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 - 2005 Tensilica Inc. + * + * Chris Zankel + * Marc Gauthier + * Joe Taylor + * Kevin Chea + */ + +#include +#include +#include +#include + +/* + * 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) + * + */ + + .macro iterate from, to , cmd + .ifeq ((\to - \from) & ~0xfff) + \cmd \from + iterate "(\from+1)", \to, \cmd + .endif + .endm + +/* + * _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. + */ + + .section .head.text, "ax" + .globl _start +_start: _j 2f + .align 4 +1: .word _startup +2: l32r a0, 1b + jx a0 + + .text + .align 4 +_startup: + + /* Disable interrupts and exceptions. */ + + movi a0, XCHAL_PS_EXCM_MASK + wsr a0, PS + + /* Preserve the pointer to the boot parameter list in EXCSAVE_1 */ + + wsr a2, EXCSAVE_1 + + /* Start with a fresh windowbase and windowstart. */ + + movi a1, 1 + movi a0, 0 + wsr a1, WINDOWSTART + wsr a0, WINDOWBASE + rsync + + /* Set a0 to 0 for the remaining initialization. */ + + movi a0, 0 + + /* Clear debugging registers. */ + +#if XCHAL_HAVE_DEBUG + wsr a0, IBREAKENABLE + wsr a0, ICOUNT + movi a1, 15 + wsr a0, ICOUNTLEVEL + + .macro reset_dbreak num + wsr a0, DBREAKC + \num + .endm + + iterate 0, XCHAL_NUM_IBREAK-1, reset_dbreak +#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. */ + + .macro reset_timer num + wsr a0, CCOMPARE_0 + \num + .endm + iterate 0, XCHAL_NUM_TIMERS-1, reset_timer + + /* Interrupt initialization. */ + + movi a2, XCHAL_INTTYPE_MASK_SOFTWARE | XCHAL_INTTYPE_MASK_EXTERN_EDGE + wsr a0, INTENABLE + wsr a2, INTCLEAR + + /* Disable coprocessors. */ + +#if XCHAL_CP_NUM > 0 + wsr a0, CPENABLE +#endif + + /* Set PS.INTLEVEL=1, PS.WOE=0, kernel stack, PS.EXCM=0 + * + * Note: PS.EXCM must be cleared before using any loop + * instructions; otherwise, they are silently disabled, and + * at most one iteration of the loop is executed. + */ + + movi a1, 1 + wsr a1, PS + rsync + + /* Initialize the caches. + * Does not include flushing writeback d-cache. + * a6, a7 are just working registers (clobbered). + */ + + icache_reset a2, a3 + dcache_reset a2, a3 + + /* 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_end # end of BSS + +1: addi a2, a2, 4 + s32i a0, a2, 0 + blt a2, a3, 1b + +#if XCHAL_DCACHE_IS_WRITEBACK + + /* After unpacking, flush the writeback cache to memory so the + * instructions/data are available. + */ + + dcache_writeback_all a2, a3 +#endif + + /* Setup stack and enable window exceptions (keep irqs disabled) */ + + movi a1, init_thread_union + addi a1, a1, KERNEL_STACK_SIZE + + movi a2, 0x00040001 # WOE=1, INTLEVEL=1, 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, EXCSAVE + XCHAL_DEBUGLEVEL + + /* Set up EXCSAVE[1] to point to the exc_table. */ + + movi a6, exc_table + xsr a6, EXCSAVE_1 + + /* 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 + + /* Define some common data structures here. We define them + * here in this assembly file due to their unusual alignment + * requirements. + */ + + .comm swapper_pg_dir,PAGE_SIZE,PAGE_SIZE + .comm empty_bad_page_table,PAGE_SIZE,PAGE_SIZE + .comm empty_bad_page,PAGE_SIZE,PAGE_SIZE + .comm empty_zero_page,PAGE_SIZE,PAGE_SIZE + diff --git a/arch/xtensa/kernel/irq.c b/arch/xtensa/kernel/irq.c new file mode 100644 index 0000000000..4cbf6d9157 --- /dev/null +++ b/arch/xtensa/kernel/irq.c @@ -0,0 +1,192 @@ +/* + * linux/arch/xtensa/kernel/irq.c + * + * Xtensa built-in interrupt controller and some generic functions copied + * from i386. + * + * Copyright (C) 2002 - 2005 Tensilica, Inc. + * Copyright (C) 1992, 1998 Linus Torvalds, Ingo Molnar + * + * + * Chris Zankel + * Kevin Chea + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +static void enable_xtensa_irq(unsigned int irq); +static void disable_xtensa_irq(unsigned int irq); +static void mask_and_ack_xtensa(unsigned int irq); +static void end_xtensa_irq(unsigned int irq); + +static unsigned int cached_irq_mask; + +atomic_t irq_err_count; + +/* + * 'what should we do if we get a hw irq event on an illegal vector'. + * each architecture has to answer this themselves. + */ +void ack_bad_irq(unsigned int irq) +{ + printk("unexpected IRQ trap at vector %02x\n", irq); +} + +/* + * do_IRQ handles all normal device IRQ's (the special + * SMP cross-CPU interrupts have their own specific + * handlers). + */ + +unsigned int do_IRQ(int irq, struct pt_regs *regs) +{ + irq_enter(); + +#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 + + __do_IRQ(irq, regs); + + irq_exit(); + + return 1; +} + +/* + * Generic, controller-independent functions: + */ + +int show_interrupts(struct seq_file *p, void *v) +{ + int i = *(loff_t *) v, j; + struct irqaction * action; + unsigned long flags; + + if (i == 0) { + seq_printf(p, " "); + for (j=0; jtypename); + seq_printf(p, " %s", action->name); + + for (action=action->next; action; action = action->next) + seq_printf(p, ", %s", action->name); + + seq_putc(p, '\n'); +skip: + spin_unlock_irqrestore(&irq_desc[i].lock, flags); + } else if (i == NR_IRQS) { + seq_printf(p, "NMI: "); + for (j = 0; j < NR_CPUS; j++) + if (cpu_online(j)) + seq_printf(p, "%10u ", nmi_count(j)); + seq_putc(p, '\n'); + seq_printf(p, "ERR: %10u\n", atomic_read(&irq_err_count)); + } + return 0; +} +/* shutdown is same as "disable" */ +#define shutdown_xtensa_irq disable_xtensa_irq + +static unsigned int startup_xtensa_irq(unsigned int irq) +{ + enable_xtensa_irq(irq); + return 0; /* never anything pending */ +} + +static struct hw_interrupt_type xtensa_irq_type = { + "Xtensa-IRQ", + startup_xtensa_irq, + shutdown_xtensa_irq, + enable_xtensa_irq, + disable_xtensa_irq, + mask_and_ack_xtensa, + end_xtensa_irq +}; + +static inline void mask_irq(unsigned int irq) +{ + cached_irq_mask &= ~(1 << irq); + set_sr (cached_irq_mask, INTENABLE); +} + +static inline void unmask_irq(unsigned int irq) +{ + cached_irq_mask |= 1 << irq; + set_sr (cached_irq_mask, INTENABLE); +} + +static void disable_xtensa_irq(unsigned int irq) +{ + unsigned long flags; + local_save_flags(flags); + mask_irq(irq); + local_irq_restore(flags); +} + +static void enable_xtensa_irq(unsigned int irq) +{ + unsigned long flags; + local_save_flags(flags); + unmask_irq(irq); + local_irq_restore(flags); +} + +static void mask_and_ack_xtensa(unsigned int irq) +{ + disable_xtensa_irq(irq); +} + +static void end_xtensa_irq(unsigned int irq) +{ + enable_xtensa_irq(irq); +} + + +void __init init_IRQ(void) +{ + int i; + + for (i=0; i < XTENSA_NR_IRQS; i++) + irq_desc[i].handler = &xtensa_irq_type; + + cached_irq_mask = 0; + + platform_init_irq(); +} diff --git a/arch/xtensa/kernel/module.c b/arch/xtensa/kernel/module.c new file mode 100644 index 0000000000..d1683cfa19 --- /dev/null +++ b/arch/xtensa/kernel/module.c @@ -0,0 +1,78 @@ +/* + * arch/xtensa/kernel/platform.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 - 2005 Tensilica Inc. + * + * Chris Zankel + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LIST_HEAD(module_buf_list); + +void *module_alloc(unsigned long size) +{ + panic("module_alloc not implemented"); +} + +void module_free(struct module *mod, void *module_region) +{ + panic("module_free not implemented"); +} + +int module_frob_arch_sections(Elf32_Ehdr *hdr, + Elf32_Shdr *sechdrs, + char *secstrings, + struct module *me) +{ + panic("module_frob_arch_sections not implemented"); +} + +int apply_relocate(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *module) +{ + panic ("apply_relocate not implemented"); +} + +int apply_relocate_add(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *module) +{ + panic("apply_relocate_add not implemented"); +} + +int module_finalize(const Elf_Ehdr *hdr, + const Elf_Shdr *sechdrs, + struct module *me) +{ + panic ("module_finalize not implemented"); +} + +void module_arch_cleanup(struct module *mod) +{ + panic("module_arch_cleanup not implemented"); +} + +struct bug_entry *module_find_bug(unsigned long bugaddr) +{ + panic("module_find_bug not implemented"); +} diff --git a/arch/xtensa/kernel/pci-dma.c b/arch/xtensa/kernel/pci-dma.c new file mode 100644 index 0000000000..84fde258cf --- /dev/null +++ b/arch/xtensa/kernel/pci-dma.c @@ -0,0 +1,73 @@ +/* + * arch/xtensa/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 + * Joe Taylor + */ + +#include +#include +#include +#include +#include +#include + +/* + * 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, int gfp) +{ + void *ret; + + /* ignore region speicifiers */ + gfp &= ~(__GFP_DMA | __GFP_HIGHMEM); + + if (dev == NULL || (*dev->dma_mask < 0xffffffff)) + gfp |= GFP_DMA; + ret = (void *)__get_free_pages(gfp, get_order(size)); + + if (ret != NULL) { + memset(ret, 0, size); + *handle = virt_to_bus(ret); + } + return (void*) BYPASS_ADDR((unsigned long)ret); +} + +void dma_free_coherent(struct device *hwdev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + free_pages(CACHED_ADDR((unsigned long)vaddr), get_order(size)); +} + + +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; + } +} diff --git a/arch/xtensa/kernel/pci.c b/arch/xtensa/kernel/pci.c new file mode 100644 index 0000000000..d29a816486 --- /dev/null +++ b/arch/xtensa/kernel/pci.c @@ -0,0 +1,563 @@ +/* + * arch/xtensa/pcibios.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 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 + * pcibios_setup + * 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; + +static void pcibios_fixup_resources(struct pci_dev* dev); + +#if 0 // FIXME +struct pci_fixup pcibios_fixups[] = { + { DECLARE_PCI_FIXUP_HEADER, PCI_ANY_ID, PCI_ANY_ID, pcibios_fixup_resources }, + { 0 } +}; +#endif + +void +pcibios_update_resource(struct pci_dev *dev, struct resource *root, + struct resource *res, int resource) +{ + u32 new, check, mask; + int reg; + struct pci_controller* pci_ctrl = dev->sysdata; + + new = res->start; + if (pci_ctrl && res->flags & IORESOURCE_IO) { + new -= pci_ctrl->io_space.base; + } + new |= (res->flags & PCI_REGION_FLAG_MASK); + if (resource < 6) { + reg = PCI_BASE_ADDRESS_0 + 4*resource; + } else if (resource == PCI_ROM_RESOURCE) { + res->flags |= PCI_ROM_ADDRESS_ENABLE; + reg = dev->rom_base_reg; + } else { + /* Somebody might have asked allocation of a non-standard resource */ + return; + } + + pci_write_config_dword(dev, reg, new); + pci_read_config_dword(dev, reg, &check); + mask = (new & PCI_BASE_ADDRESS_SPACE_IO) ? + PCI_BASE_ADDRESS_IO_MASK : PCI_BASE_ADDRESS_MEM_MASK; + + if ((new ^ check) & mask) { + printk(KERN_ERR "PCI: Error while updating region " + "%s/%d (%08x != %08x)\n", dev->slot_name, resource, + new, check); + } +} + +/* + * 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.. + */ +void +pcibios_align_resource(void *data, struct resource *res, unsigned long size, + unsigned long align) +{ + struct pci_dev *dev = data; + + if (res->flags & IORESOURCE_IO) { + unsigned long start = res->start; + + if (size > 0x100) { + printk(KERN_ERR "PCI: I/O Region %s/%d too large" + " (%ld bytes)\n", dev->slot_name, + dev->resource - res, size); + } + + if (start & 0x300) { + start = (start + 0x3ff) & ~0x3ff; + res->start = 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", dev->slot_name); + 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", + dev->slot_name, 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 int __init pcibios_init(void) +{ + struct pci_controller *pci_ctrl; + struct pci_bus *bus; + int next_busno = 0, i; + + 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; + bus = pci_scan_bus(pci_ctrl->first_busno, pci_ctrl->ops, + pci_ctrl); + if (pci_ctrl->io_resource.flags) { + unsigned long offs; + + offs = (unsigned long)pci_ctrl->io_space.base; + pci_ctrl->io_resource.start += offs; + pci_ctrl->io_resource.end += offs; + bus->resource[0] = &pci_ctrl->io_resource; + } + for (i = 0; i < 3; ++i) + if (pci_ctrl->mem_resources[i].flags) + bus->resource[i+1] =&pci_ctrl->mem_resources[i]; + pci_ctrl->bus = bus; + pci_ctrl->last_busno = bus->subordinate; + 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 __init pcibios_fixup_bus(struct pci_bus *bus) +{ + struct pci_controller *pci_ctrl = bus->sysdata; + struct resource *res; + unsigned long io_offset; + int i; + + io_offset = (unsigned long)pci_ctrl->io_space.base; + if (bus->parent == NULL) { + /* this is a host bridge - fill in its resources */ + pci_ctrl->bus = bus; + + bus->resource[0] = 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; + + 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; + } + bus->resource[i+1] = res; + } + } else { + /* This is a subordinate bridge */ + pci_read_bridge_bases(bus); + + for (i = 0; i < 4; i++) { + if ((res = bus->resource[i]) == NULL || !res->flags) + continue; + if (io_offset && (res->flags & IORESOURCE_IO)) { + res->start += io_offset; + res->end += io_offset; + } + } + } +} + +char __init *pcibios_setup(char *str) +{ + return str; +} + +/* the next one is stolen from the alpha port... */ + +void __init +pcibios_update_irq(struct pci_dev *dev, int irq) +{ + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq); +} + +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", dev->slot_name); + 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", + dev->slot_name, 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 */ + + +static void +pcibios_fixup_resources(struct pci_dev *dev) +{ + struct pci_controller* pci_ctrl = (struct pci_controller *)dev->sysdata; + int i; + unsigned long offset; + + if (!pci_ctrl) { + printk(KERN_ERR "No pci_ctrl for PCI dev %s!\n",dev->slot_name); + return; + } + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { + struct resource *res = dev->resource + i; + if (!res->start || !res->flags) + continue; + if (res->end == 0xffffffff) { + DBG("PCI:%s Resource %d [%08lx-%08lx] is unassigned\n", + dev->slot_name, i, res->start, res->end); + res->end -= res->start; + res->start = 0; + continue; + } + offset = 0; + if (res->flags & IORESOURCE_IO) + offset = (unsigned long) pci_ctrl->io_space.base; + else if (res->flags & IORESOURCE_MEM) + offset = (unsigned long) pci_ctrl->mem_space.base; + + if (offset != 0) { + res->start += offset; + res->end += offset; +#ifdef DEBUG + printk("Fixup res %d (%lx) of dev %s: %lx -> %lx\n", + i, res->flags, dev->slot_name, + res->start - offset, res->start); +#endif + } + } +} + +/* + * 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_flags of VMA, as appropriate for this architecture, for a pci device + * mapping. + */ +static __inline__ void +__pci_mmap_set_flags(struct pci_dev *dev, struct vm_area_struct *vma, + enum pci_mmap_state mmap_state) +{ + vma->vm_flags |= VM_SHM | VM_LOCKED | VM_IO; +} + +/* + * 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 &= ~_PAGE_NO_CACHE; +#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_flags(dev, vma, mmap_state); + __pci_mmap_set_pgprot(dev, vma, mmap_state, write_combine); + + ret = io_remap_page_range(vma, vma->vm_start, vma->vm_pgoff<vm_end - vma->vm_start, vma->vm_page_prot); + + return ret; +} + +/* + * This probably belongs here rather than ioport.c because + * we do not want this crud linked into SBus kernels. + * Also, think for a moment about likes of floppy.c that + * include architecture specific parts. They may want to redefine ins/outs. + * + * We do not use horroble macroses here because we want to + * advance pointer by sizeof(size). + */ +void outsb(unsigned long addr, const void *src, unsigned long count) { + while (count) { + count -= 1; + writeb(*(const char *)src, addr); + src += 1; + addr += 1; + } +} + +void outsw(unsigned long addr, const void *src, unsigned long count) { + while (count) { + count -= 2; + writew(*(const short *)src, addr); + src += 2; + addr += 2; + } +} + +void outsl(unsigned long addr, const void *src, unsigned long count) { + while (count) { + count -= 4; + writel(*(const long *)src, addr); + src += 4; + addr += 4; + } +} + +void insb(unsigned long addr, void *dst, unsigned long count) { + while (count) { + count -= 1; + *(unsigned char *)dst = readb(addr); + dst += 1; + addr += 1; + } +} + +void insw(unsigned long addr, void *dst, unsigned long count) { + while (count) { + count -= 2; + *(unsigned short *)dst = readw(addr); + dst += 2; + addr += 2; + } +} + +void insl(unsigned long addr, void *dst, unsigned long count) { + while (count) { + count -= 4; + /* + * XXX I am sure we are in for an unaligned trap here. + */ + *(unsigned long *)dst = readl(addr); + dst += 4; + addr += 4; + } +} + + + diff --git a/arch/xtensa/kernel/platform.c b/arch/xtensa/kernel/platform.c new file mode 100644 index 0000000000..cf13627844 --- /dev/null +++ b/arch/xtensa/kernel/platform.c @@ -0,0 +1,49 @@ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include + +#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, init_irq, (void), { }); +_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(int, get_rtc_time, (time_t* t), { return 0; }); +_F(int, set_rtc_time, (time_t t), { return 0; }); + +#if CONFIG_XTENSA_CALIBRATE_CCOUNT +_F(void, calibrate_ccount, (void), +{ + printk ("ERROR: Cannot calibrate cpu frequency! Assuming 100MHz.\n"); + ccount_per_jiffy = 100 * (1000000UL/HZ); +}); +#endif + diff --git a/arch/xtensa/kernel/process.c b/arch/xtensa/kernel/process.c new file mode 100644 index 0000000000..4099703b14 --- /dev/null +++ b/arch/xtensa/kernel/process.c @@ -0,0 +1,482 @@ +// TODO verify coprocessor handling +/* + * 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 + * Chris Zankel + * Marc Gauthier + * Kevin Chea + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern void ret_from_fork(void); + +static struct fs_struct init_fs = INIT_FS; +static struct files_struct init_files = INIT_FILES; +static struct signal_struct init_signals = INIT_SIGNALS(init_signals); +static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand); +struct mm_struct init_mm = INIT_MM(init_mm); +EXPORT_SYMBOL(init_mm); + +union thread_union init_thread_union + __attribute__((__section__(".data.init_task"))) = +{ INIT_THREAD_INFO(init_task) }; + +struct task_struct init_task = INIT_TASK(init_task); +EXPORT_SYMBOL(init_task); + +struct task_struct *current_set[NR_CPUS] = {&init_task, }; + + +#if XCHAL_CP_NUM > 0 + +/* + * Coprocessor ownership. + */ + +coprocessor_info_t coprocessor_info[] = { + { 0, XTENSA_CPE_CP0_OFFSET }, + { 0, XTENSA_CPE_CP1_OFFSET }, + { 0, XTENSA_CPE_CP2_OFFSET }, + { 0, XTENSA_CPE_CP3_OFFSET }, + { 0, XTENSA_CPE_CP4_OFFSET }, + { 0, XTENSA_CPE_CP5_OFFSET }, + { 0, XTENSA_CPE_CP6_OFFSET }, + { 0, XTENSA_CPE_CP7_OFFSET }, +}; + +#endif + +/* + * Powermanagement idle function, if any is provided by the platform. + */ + +void cpu_idle(void) +{ + local_irq_enable(); + + /* endless idle loop with no priority at all */ + while (1) { + while (!need_resched()) + platform_idle(); + preempt_enable(); + schedule(); + } +} + +/* + * Free current thread data structures etc.. + */ + +void exit_thread(void) +{ + release_coprocessors(current); /* Empty macro if no CPs are defined */ +} + +void flush_thread(void) +{ + release_coprocessors(current); /* Empty macro if no CPs are defined */ +} + +/* + * Copy thread. + * + * The stack layout for the new thread looks like this: + * + * +------------------------+ <- sp in childregs (= tos) + * | childregs | + * +------------------------+ <- thread.sp = sp in dummy-frame + * | dummy-frame | (saved in dummy-frame spill-area) + * +------------------------+ + * + * We create a dummy frame to return to ret_from_fork: + * a0 points to ret_from_fork (simulating a call4) + * sp points to itself (thread.sp) + * a2, a3 are unused. + * + * Note: This is a pristine frame, so we don't need any spill region on top of + * childregs. + */ + +int copy_thread(int nr, unsigned long clone_flags, unsigned long usp, + unsigned long unused, + struct task_struct * p, struct pt_regs * regs) +{ + struct pt_regs *childregs; + unsigned long tos; + int user_mode = user_mode(regs); + + /* Set up new TSS. */ + tos = (unsigned long)p->thread_info + THREAD_SIZE; + if (user_mode) + childregs = (struct pt_regs*)(tos - PT_USER_SIZE); + else + childregs = (struct pt_regs*)tos - 1; + + *childregs = *regs; + + /* Create a call4 dummy-frame: a0 = 0, a1 = childregs. */ + *((int*)childregs - 3) = (unsigned long)childregs; + *((int*)childregs - 4) = 0; + + childregs->areg[1] = tos; + childregs->areg[2] = 0; + p->set_child_tid = p->clear_child_tid = NULL; + p->thread.ra = MAKE_RA_FOR_CALL((unsigned long)ret_from_fork, 0x1); + p->thread.sp = (unsigned long)childregs; + if (user_mode(regs)) { + + int len = childregs->wmask & ~0xf; + childregs->areg[1] = usp; + memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], + ®s->areg[XCHAL_NUM_AREGS - len/4], len); + + if (clone_flags & CLONE_SETTLS) + childregs->areg[2] = childregs->areg[6]; + + } else { + /* In kernel space, we start a new thread with a new stack. */ + childregs->wmask = 1; + } + return 0; +} + + +/* + * Create a kernel thread + */ + +int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) +{ + long retval; + __asm__ __volatile__ + ("mov a5, %4\n\t" /* preserve fn in a5 */ + "mov a6, %3\n\t" /* preserve and setup arg in a6 */ + "movi a2, %1\n\t" /* load __NR_clone for syscall*/ + "mov a3, sp\n\t" /* sp check and sys_clone */ + "mov a4, %5\n\t" /* load flags for syscall */ + "syscall\n\t" + "beq a3, sp, 1f\n\t" /* branch if parent */ + "callx4 a5\n\t" /* call fn */ + "movi a2, %2\n\t" /* load __NR_exit for syscall */ + "mov a3, a6\n\t" /* load fn return value */ + "syscall\n" + "1:\n\t" + "mov %0, a2\n\t" /* parent returns zero */ + :"=r" (retval) + :"i" (__NR_clone), "i" (__NR_exit), + "r" (arg), "r" (fn), + "r" (flags | CLONE_VM) + : "a2", "a3", "a4", "a5", "a6" ); + return retval; +} + + +/* + * These bracket the sleeping functions.. + */ + +unsigned long get_wchan(struct task_struct *p) +{ + unsigned long sp, pc; + unsigned long stack_page = (unsigned long) p->thread_info; + 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; +} + +/* + * do_copy_regs() gathers information from 'struct pt_regs' and + * 'current->thread.areg[]' to fill in the xtensa_gregset_t + * structure. + * + * 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 do_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs, + struct task_struct *tsk) +{ + int i, n, wb_offset; + + elfregs->xchal_config_id0 = XCHAL_HW_CONFIGID0; + elfregs->xchal_config_id1 = XCHAL_HW_CONFIGID1; + + __asm__ __volatile__ ("rsr %0, 176\n" : "=a" (i)); + elfregs->cpux = i; + __asm__ __volatile__ ("rsr %0, 208\n" : "=a" (i)); + elfregs->cpuy = i; + + /* 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 & ~XCHAL_PS_EXCM_MASK); + elfregs->exccause = regs->exccause; + elfregs->excvaddr = regs->excvaddr; + elfregs->windowbase = regs->windowbase; + elfregs->windowstart = regs->windowstart; + elfregs->lbeg = regs->lbeg; + elfregs->lend = regs->lend; + elfregs->lcount = regs->lcount; + elfregs->sar = regs->sar; + elfregs->syscall = regs->syscall; + + /* Copy register file. + * The layout looks like this: + * + * | a0 ... a15 | Z ... Z | arX ... arY | + * current window unused saved frames + */ + + memset (elfregs->ar, 0, sizeof(elfregs->ar)); + + wb_offset = regs->windowbase * 4; + n = (regs->wmask&1)? 4 : (regs->wmask&2)? 8 : (regs->wmask&4)? 12 : 16; + + for (i = 0; i < n; i++) + elfregs->ar[(wb_offset + i) % XCHAL_NUM_AREGS] = regs->areg[i]; + + n = (regs->wmask >> 4) * 4; + + for (i = XCHAL_NUM_AREGS - n; n > 0; i++, n--) + elfregs->ar[(wb_offset + i) % XCHAL_NUM_AREGS] = regs->areg[i]; +} + +void xtensa_elf_core_copy_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs) +{ + do_copy_regs ((xtensa_gregset_t *)elfregs, regs, current); +} + + +/* The inverse of do_copy_regs(). No error or sanity checking. */ + +void do_restore_regs (xtensa_gregset_t *elfregs, struct pt_regs *regs, + struct task_struct *tsk) +{ + int i, n, wb_offset; + + /* Note: PS.EXCM is not set while user task is running; it + * needs to be set in regs->ps is for exception handling convenience. + */ + + regs->pc = elfregs->pc; + regs->ps = (elfregs->ps | XCHAL_PS_EXCM_MASK); + regs->exccause = elfregs->exccause; + regs->excvaddr = elfregs->excvaddr; + regs->windowbase = elfregs->windowbase; + regs->windowstart = elfregs->windowstart; + regs->lbeg = elfregs->lbeg; + regs->lend = elfregs->lend; + regs->lcount = elfregs->lcount; + regs->sar = elfregs->sar; + regs->syscall = elfregs->syscall; + + /* Clear everything. */ + + memset (regs->areg, 0, sizeof(regs->areg)); + + /* Copy regs from live window frame. */ + + wb_offset = regs->windowbase * 4; + n = (regs->wmask&1)? 4 : (regs->wmask&2)? 8 : (regs->wmask&4)? 12 : 16; + + for (i = 0; i < n; i++) + regs->areg[(wb_offset+i) % XCHAL_NUM_AREGS] = elfregs->ar[i]; + + n = (regs->wmask >> 4) * 4; + + for (i = XCHAL_NUM_AREGS - n; n > 0; i++, n--) + regs->areg[(wb_offset+i) % XCHAL_NUM_AREGS] = elfregs->ar[i]; +} + +/* + * do_save_fpregs() gathers information from 'struct pt_regs' and + * 'current->thread' to fill in the elf_fpregset_t structure. + * + * Core files and ptrace use elf_fpregset_t. + */ + +void do_save_fpregs (elf_fpregset_t *fpregs, struct pt_regs *regs, + struct task_struct *tsk) +{ +#if XCHAL_HAVE_CP + + extern unsigned char _xtensa_reginfo_tables[]; + extern unsigned _xtensa_reginfo_table_size; + int i; + unsigned long flags; + + /* Before dumping coprocessor state from memory, + * ensure any live coprocessor contents for this + * task are first saved to memory: + */ + local_irq_save(flags); + + for (i = 0; i < XCHAL_CP_MAX; i++) { + if (tsk == coprocessor_info[i].owner) { + enable_coprocessor(i); + save_coprocessor_registers( + tsk->thread.cp_save+coprocessor_info[i].offset,i); + disable_coprocessor(i); + } + } + + local_irq_restore(flags); + + /* Now dump coprocessor & extra state: */ + memcpy((unsigned char*)fpregs, + _xtensa_reginfo_tables, _xtensa_reginfo_table_size); + memcpy((unsigned char*)fpregs + _xtensa_reginfo_table_size, + tsk->thread.cp_save, XTENSA_CP_EXTRA_SIZE); +#endif +} + +/* + * The inverse of do_save_fpregs(). + * Copies coprocessor and extra state from fpregs into regs and tsk->thread. + * Returns 0 on success, non-zero if layout doesn't match. + */ + +int do_restore_fpregs (elf_fpregset_t *fpregs, struct pt_regs *regs, + struct task_struct *tsk) +{ +#if XCHAL_HAVE_CP + + extern unsigned char _xtensa_reginfo_tables[]; + extern unsigned _xtensa_reginfo_table_size; + int i; + unsigned long flags; + + /* Make sure save area layouts match. + * FIXME: in the future we could allow restoring from + * a different layout of the same registers, by comparing + * fpregs' table with _xtensa_reginfo_tables and matching + * entries and copying registers one at a time. + * Not too sure yet whether that's very useful. + */ + + if( memcmp((unsigned char*)fpregs, + _xtensa_reginfo_tables, _xtensa_reginfo_table_size) ) { + return -1; + } + + /* Before restoring coprocessor state from memory, + * ensure any live coprocessor contents for this + * task are first invalidated. + */ + + local_irq_save(flags); + + for (i = 0; i < XCHAL_CP_MAX; i++) { + if (tsk == coprocessor_info[i].owner) { + enable_coprocessor(i); + save_coprocessor_registers( + tsk->thread.cp_save+coprocessor_info[i].offset,i); + coprocessor_info[i].owner = 0; + disable_coprocessor(i); + } + } + + local_irq_restore(flags); + + /* Now restore coprocessor & extra state: */ + + memcpy(tsk->thread.cp_save, + (unsigned char*)fpregs + _xtensa_reginfo_table_size, + XTENSA_CP_EXTRA_SIZE); +#endif + return 0; +} +/* + * Fill in the CP structure for a core dump for a particular task. + */ + +int +dump_task_fpu(struct pt_regs *regs, struct task_struct *task, elf_fpregset_t *r) +{ +/* see asm/coprocessor.h for this magic number 16 */ +#if TOTAL_CPEXTRA_SIZE > 16 + do_save_fpregs (r, regs, task); + + /* For now, bit 16 means some extra state may be present: */ +// FIXME!! need to track to return more accurate mask + return 0x10000 | XCHAL_CP_MASK; +#else + return 0; /* no coprocessors active on this processor */ +#endif +} + +/* + * Fill in the CP structure for a core dump. + * This includes any FPU coprocessor. + * Here, we dump all coprocessors, and other ("extra") custom state. + * + * This function is called by elf_core_dump() in fs/binfmt_elf.c + * (in which case 'regs' comes from calls to do_coredump, see signals.c). + */ +int dump_fpu(struct pt_regs *regs, elf_fpregset_t *r) +{ + return dump_task_fpu(regs, current, r); +} diff --git a/arch/xtensa/kernel/ptrace.c b/arch/xtensa/kernel/ptrace.c new file mode 100644 index 0000000000..9ef07a4dd2 --- /dev/null +++ b/arch/xtensa/kernel/ptrace.c @@ -0,0 +1,407 @@ +// 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 - 2005 Tensilica Inc. + * + * Joe Taylor + * Chris Zankel + * Scott Foehner, + * Kevin Chea + * Marc Gauthier + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define TEST_KERNEL // verify kernel operations FIXME: remove + + +/* + * Called by kernel/ptrace.c when detaching.. + * + * Make sure single step bits etc are not set. + */ + +void ptrace_disable(struct task_struct *child) +{ + /* Nothing to do.. */ +} + +int sys_ptrace(long request, long pid, long addr, long data) +{ + struct task_struct *child; + int ret = -EPERM; + + lock_kernel(); + +#if 0 + if ((int)request != 1) + printk("ptrace(r=%d,pid=%d,addr=%08lx,data=%08lx)\n", + (int) request, (int) pid, (unsigned long) addr, + (unsigned long) data); +#endif + + if (request == PTRACE_TRACEME) { + + /* Are we already being traced? */ + + if (current->ptrace & PT_PTRACED) + goto out; + + if ((ret = security_ptrace(current->parent, current))) + goto out; + + /* Set the ptrace bit in the process flags. */ + + current->ptrace |= PT_PTRACED; + ret = 0; + goto out; + } + + ret = -ESRCH; + read_lock(&tasklist_lock); + child = find_task_by_pid(pid); + if (child) + get_task_struct(child); + read_unlock(&tasklist_lock); + if (!child) + goto out; + + ret = -EPERM; + if (pid == 1) /* you may not mess with init */ + goto out; + + if (request == PTRACE_ATTACH) { + ret = ptrace_attach(child); + goto out_tsk; + } + + if ((ret = ptrace_check_attach(child, request == PTRACE_KILL)) < 0) + goto out_tsk; + + switch (request) { + case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKDATA: + { + unsigned long tmp; + int copied; + + copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); + ret = -EIO; + if (copied != sizeof(tmp)) + break; + ret = put_user(tmp,(unsigned long *) data); + + goto out; + } + + /* Read the word at location addr in the USER area. */ + + case PTRACE_PEEKUSR: + { + struct pt_regs *regs; + unsigned long tmp; + + regs = xtensa_pt_regs(child); + tmp = 0; /* Default return value. */ + + switch(addr) { + + case REG_AR_BASE ... REG_AR_BASE + XCHAL_NUM_AREGS - 1: + { + int ar = addr - REG_AR_BASE - regs->windowbase * 4; + ar &= (XCHAL_NUM_AREGS - 1); + if (ar < 16 && ar + (regs->wmask >> 4) * 4 >= 0) + tmp = regs->areg[ar]; + else + ret = -EIO; + break; + } + case REG_A_BASE ... REG_A_BASE + 15: + tmp = regs->areg[addr - 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 & ~XCHAL_PS_EXCM_MASK); + break; + case REG_WB: + tmp = regs->windowbase; + break; + case REG_WS: + tmp = regs->windowstart; + 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 REG_DEPC: + tmp = regs->depc; + break; + case REG_EXCCAUSE: + tmp = regs->exccause; + break; + case REG_EXCVADDR: + tmp = regs->excvaddr; + break; + case SYSCALL_NR: + tmp = regs->syscall; + break; + default: + tmp = 0; + ret = -EIO; + goto out; + } + ret = put_user(tmp, (unsigned long *) data); + goto out; + } + + case PTRACE_POKETEXT: /* write the word at location addr. */ + case PTRACE_POKEDATA: + if (access_process_vm(child, addr, &data, sizeof(data), 1) + == sizeof(data)) + break; + ret = -EIO; + goto out; + + case PTRACE_POKEUSR: + { + struct pt_regs *regs; + regs = xtensa_pt_regs(child); + + switch (addr) { + case REG_AR_BASE ... REG_AR_BASE + XCHAL_NUM_AREGS - 1: + { + int ar = addr - REG_AR_BASE - regs->windowbase * 4; + if (ar < 16 && ar + (regs->wmask >> 4) * 4 >= 0) + regs->areg[ar & (XCHAL_NUM_AREGS - 1)] = data; + else + ret = -EIO; + break; + } + case REG_A_BASE ... REG_A_BASE + 15: + regs->areg[addr - REG_A_BASE] = data; + break; + case REG_PC: + regs->pc = data; + break; + case SYSCALL_NR: + regs->syscall = data; + break; +#ifdef TEST_KERNEL + case REG_WB: + regs->windowbase = data; + break; + case REG_WS: + regs->windowstart = data; + break; +#endif + + default: + /* The rest are not allowed. */ + ret = -EIO; + break; + } + break; + } + + /* continue and stop at next (return from) syscall */ + case PTRACE_SYSCALL: + case PTRACE_CONT: /* restart after signal. */ + { + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + if (request == PTRACE_SYSCALL) + set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + else + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + child->exit_code = data; + /* Make sure the single step bit is not set. */ + child->ptrace &= ~PT_SINGLESTEP; + wake_up_process(child); + ret = 0; + break; + } + + /* + * make the child exit. Best I can do is send it a sigkill. + * perhaps it should be put in the status that it wants to + * exit. + */ + case PTRACE_KILL: + ret = 0; + if (child->state == EXIT_ZOMBIE) /* already dead */ + break; + child->exit_code = SIGKILL; + child->ptrace &= ~PT_SINGLESTEP; + wake_up_process(child); + break; + + case PTRACE_SINGLESTEP: + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + child->ptrace |= PT_SINGLESTEP; + child->exit_code = data; + wake_up_process(child); + ret = 0; + break; + + case PTRACE_GETREGS: + { + /* 'data' points to user memory in which to write. + * Mainly due to the non-live register values, we + * reformat the register values into something more + * standard. For convenience, we use the handy + * elf_gregset_t format. */ + + xtensa_gregset_t format; + struct pt_regs *regs = xtensa_pt_regs(child); + + do_copy_regs (&format, regs, child); + + /* Now, copy to user space nice and easy... */ + ret = 0; + if (copy_to_user((void *)data, &format, sizeof(elf_gregset_t))) + ret = -EFAULT; + break; + } + + case PTRACE_SETREGS: + { + /* 'data' points to user memory that contains the new + * values in the elf_gregset_t format. */ + + xtensa_gregset_t format; + struct pt_regs *regs = xtensa_pt_regs(child); + + if (copy_from_user(&format,(void *)data,sizeof(elf_gregset_t))){ + ret = -EFAULT; + break; + } + + /* FIXME: Perhaps we want some sanity checks on + * these user-space values? See ARM version. Are + * debuggers a security concern? */ + + do_restore_regs (&format, regs, child); + + ret = 0; + break; + } + + case PTRACE_GETFPREGS: + { + /* 'data' points to user memory in which to write. + * For convenience, we use the handy + * elf_fpregset_t format. */ + + elf_fpregset_t fpregs; + struct pt_regs *regs = xtensa_pt_regs(child); + + do_save_fpregs (&fpregs, regs, child); + + /* Now, copy to user space nice and easy... */ + ret = 0; + if (copy_to_user((void *)data, &fpregs, sizeof(elf_fpregset_t))) + ret = -EFAULT; + + break; + } + + case PTRACE_SETFPREGS: + { + /* 'data' points to user memory that contains the new + * values in the elf_fpregset_t format. + */ + elf_fpregset_t fpregs; + struct pt_regs *regs = xtensa_pt_regs(child); + + ret = 0; + if (copy_from_user(&fpregs, (void *)data, sizeof(elf_fpregset_t))) { + ret = -EFAULT; + break; + } + + if (do_restore_fpregs (&fpregs, regs, child)) + ret = -EIO; + break; + } + + case PTRACE_GETFPREGSIZE: + /* 'data' points to 'unsigned long' set to the size + * of elf_fpregset_t + */ + ret = put_user(sizeof(elf_fpregset_t), (unsigned long *) data); + break; + + case PTRACE_DETACH: /* detach a process that was attached. */ + ret = ptrace_detach(child, data); + break; + + default: + ret = ptrace_request(child, request, addr, data); + goto out; + } +out_tsk: + put_task_struct(child); +out: + unlock_kernel(); + return ret; +} + +void do_syscall_trace(void) +{ + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return; + + if (!(current->ptrace & PT_PTRACED)) + return; + + /* + * 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; + } +} diff --git a/arch/xtensa/kernel/semaphore.c b/arch/xtensa/kernel/semaphore.c new file mode 100644 index 0000000000..d40f4b1b75 --- /dev/null +++ b/arch/xtensa/kernel/semaphore.c @@ -0,0 +1,226 @@ +/* + * arch/xtensa/kernel/semaphore.c + * + * Generic semaphore code. Buyer beware. Do your own specific changes + * in + * + * 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 + * Chris Zankel + * Marc Gauthier + * Kevin Chea + */ + +#include +#include +#include +#include +#include + +/* + * These two _must_ execute atomically wrt each other. + */ + +static __inline__ void wake_one_more(struct semaphore * sem) +{ + atomic_inc((atomic_t *)&sem->sleepers); +} + +static __inline__ int waking_non_zero(struct semaphore *sem) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&semaphore_wake_lock, flags); + if (sem->sleepers > 0) { + sem->sleepers--; + ret = 1; + } + spin_unlock_irqrestore(&semaphore_wake_lock, flags); + return ret; +} + +/* + * waking_non_zero_interruptible: + * 1 got the lock + * 0 go to sleep + * -EINTR interrupted + * + * We must undo the sem->count down_interruptible() increment while we are + * protected by the spinlock in order to make atomic this atomic_inc() with the + * atomic_read() in wake_one_more(), otherwise we can race. -arca + */ + +static __inline__ int waking_non_zero_interruptible(struct semaphore *sem, + struct task_struct *tsk) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&semaphore_wake_lock, flags); + if (sem->sleepers > 0) { + sem->sleepers--; + ret = 1; + } else if (signal_pending(tsk)) { + atomic_inc(&sem->count); + ret = -EINTR; + } + spin_unlock_irqrestore(&semaphore_wake_lock, flags); + return ret; +} + +/* + * waking_non_zero_trylock: + * 1 failed to lock + * 0 got the lock + * + * We must undo the sem->count down_trylock() increment while we are + * protected by the spinlock in order to make atomic this atomic_inc() with the + * atomic_read() in wake_one_more(), otherwise we can race. -arca + */ + +static __inline__ int waking_non_zero_trylock(struct semaphore *sem) +{ + unsigned long flags; + int ret = 1; + + spin_lock_irqsave(&semaphore_wake_lock, flags); + if (sem->sleepers <= 0) + atomic_inc(&sem->count); + else { + sem->sleepers--; + ret = 0; + } + spin_unlock_irqrestore(&semaphore_wake_lock, flags); + return ret; +} + +spinlock_t semaphore_wake_lock; + +/* + * Semaphores are implemented using a two-way counter: + * The "count" variable is decremented for each process + * that tries to sleep, while the "waking" variable is + * incremented when the "up()" code goes to wake up waiting + * processes. + * + * Notably, the inline "up()" and "down()" functions can + * efficiently test if they need to do any extra work (up + * needs to do something only if count was negative before + * the increment operation. + * + * waking_non_zero() (from asm/semaphore.h) must execute + * atomically. + * + * When __up() is called, the count was negative before + * incrementing it, and we need to wake up somebody. + * + * This routine adds one to the count of processes that need to + * wake up and exit. ALL waiting processes actually wake up but + * only the one that gets to the "waking" field first will gate + * through and acquire the semaphore. The others will go back + * to sleep. + * + * Note that these functions are only called when there is + * contention on the lock, and as such all this is the + * "non-critical" part of the whole semaphore business. The + * critical part is the inline stuff in + * where we want to avoid any extra jumps and calls. + */ + +void __up(struct semaphore *sem) +{ + wake_one_more(sem); + wake_up(&sem->wait); +} + +/* + * Perform the "down" function. Return zero for semaphore acquired, + * return negative for signalled out of the function. + * + * If called from __down, the return is ignored and the wait loop is + * not interruptible. This means that a task waiting on a semaphore + * using "down()" cannot be killed until someone does an "up()" on + * the semaphore. + * + * If called from __down_interruptible, the return value gets checked + * upon return. If the return value is negative then the task continues + * with the negative value in the return register (it can be tested by + * the caller). + * + * Either form may be used in conjunction with "up()". + * + */ + +#define DOWN_VAR \ + struct task_struct *tsk = current; \ + wait_queue_t wait; \ + init_waitqueue_entry(&wait, tsk); + +#define DOWN_HEAD(task_state) \ + \ + \ + tsk->state = (task_state); \ + add_wait_queue(&sem->wait, &wait); \ + \ + /* \ + * Ok, we're set up. sem->count is known to be less than zero \ + * so we must wait. \ + * \ + * We can let go the lock for purposes of waiting. \ + * We re-acquire it after awaking so as to protect \ + * all semaphore operations. \ + * \ + * If "up()" is called before we call waking_non_zero() then \ + * we will catch it right away. If it is called later then \ + * we will have to go through a wakeup cycle to catch it. \ + * \ + * Multiple waiters contend for the semaphore lock to see \ + * who gets to gate through and who has to wait some more. \ + */ \ + for (;;) { + +#define DOWN_TAIL(task_state) \ + tsk->state = (task_state); \ + } \ + tsk->state = TASK_RUNNING; \ + remove_wait_queue(&sem->wait, &wait); + +void __sched __down(struct semaphore * sem) +{ + DOWN_VAR + DOWN_HEAD(TASK_UNINTERRUPTIBLE) + if (waking_non_zero(sem)) + break; + schedule(); + DOWN_TAIL(TASK_UNINTERRUPTIBLE) +} + +int __sched __down_interruptible(struct semaphore * sem) +{ + int ret = 0; + DOWN_VAR + DOWN_HEAD(TASK_INTERRUPTIBLE) + + ret = waking_non_zero_interruptible(sem, tsk); + if (ret) + { + if (ret == 1) + /* ret != 0 only if we get interrupted -arca */ + ret = 0; + break; + } + schedule(); + DOWN_TAIL(TASK_INTERRUPTIBLE) + return ret; +} + +int __down_trylock(struct semaphore * sem) +{ + return waking_non_zero_trylock(sem); +} diff --git a/arch/xtensa/kernel/setup.c b/arch/xtensa/kernel/setup.c new file mode 100644 index 0000000000..1f5bf5d624 --- /dev/null +++ b/arch/xtensa/kernel/setup.c @@ -0,0 +1,520 @@ +/* + * arch/xtensa/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 + * Joe Taylor + * Kevin Chea + * Marc Gauthier + */ + +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE) +# include +#endif + +#ifdef CONFIG_RTC +# include +#endif + +#ifdef CONFIG_PROC_FS +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + +#if defined(CONFIG_BLK_DEV_IDE) || defined(CONFIG_BLK_DEV_IDE_MODULE) +extern struct ide_ops no_ide_ops; +struct ide_ops *ide_ops; +#endif + +extern struct rtc_ops no_rtc_ops; +struct rtc_ops *rtc_ops; + +#ifdef CONFIG_PC_KEYB +extern struct kbd_ops no_kbd_ops; +struct kbd_ops *kbd_ops; +#endif + +#ifdef CONFIG_BLK_DEV_INITRD +extern void *initrd_start; +extern void *initrd_end; +extern void *__initrd_start; +extern void *__initrd_end; +int initrd_is_mapped = 0; +extern int initrd_below_start_ok; +#endif + +unsigned char aux_device_present; +extern unsigned long loops_per_jiffy; + +/* Command line specified as configuration option. */ + +static char command_line[COMMAND_LINE_SIZE]; + +#ifdef CONFIG_CMDLINE_BOOL +static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; +#endif + +sysmem_info_t __initdata sysmem; + +#ifdef CONFIG_BLK_DEV_INITRD +int initrd_is_mapped; +#endif + +extern void init_mmu(void); + +/* + * 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__((unused, __section__(".taglist"))) = { tag, fn } + +/* parse current tag */ + +static int __init parse_tag_mem(const bp_tag_t *tag) +{ + meminfo_t *mi = (meminfo_t*)(tag->data); + + if (mi->type != MEMORY_TYPE_CONVENTIONAL) + return -1; + + if (sysmem.nr_banks >= SYSMEM_BANKS_MAX) { + printk(KERN_WARNING + "Ignoring memory bank 0x%08lx size %ldKB\n", + (unsigned long)mi->start, + (unsigned long)mi->end - (unsigned long)mi->start); + return -EINVAL; + } + sysmem.bank[sysmem.nr_banks].type = mi->type; + sysmem.bank[sysmem.nr_banks].start = PAGE_ALIGN(mi->start); + sysmem.bank[sysmem.nr_banks].end = mi->end & PAGE_SIZE; + sysmem.nr_banks++; + + return 0; +} + +__tagtable(BP_TAG_MEMORY, parse_tag_mem); + +#ifdef CONFIG_BLK_DEV_INITRD + +static int __init parse_tag_initrd(const bp_tag_t* tag) +{ + meminfo_t* mi; + mi = (meminfo_t*)(tag->data); + initrd_start = (void*)(mi->start); + initrd_end = (void*)(mi->end); + + return 0; +} + +__tagtable(BP_TAG_INITRD, parse_tag_initrd); + +#endif /* CONFIG_BLK_DEV_INITRD */ + +static int __init parse_tag_cmdline(const bp_tag_t* tag) +{ + strncpy(command_line, (char*)(tag->data), COMMAND_LINE_SIZE); + command_line[COMMAND_LINE_SIZE - 1] = '\0'; + 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; +} + +/* + * Initialize architecture. (Early stage) + */ + +void __init init_arch(bp_tag_t *bp_start) +{ + +#ifdef CONFIG_BLK_DEV_INITRD + initrd_start = &__initrd_start; + initrd_end = &__initrd_end; +#endif + + sysmem.nr_banks = 0; + +#ifdef CONFIG_CMDLINE_BOOL + strcpy(command_line, default_command_line); +#endif + + /* Parse boot parameters */ + + if (bp_start) + parse_bootparam(bp_start); + + if (sysmem.nr_banks == 0) { + sysmem.nr_banks = 1; + sysmem.bank[0].start = PLATFORM_DEFAULT_MEM_START; + sysmem.bank[0].end = PLATFORM_DEFAULT_MEM_START + + PLATFORM_DEFAULT_MEM_SIZE; + } + + /* 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; + +void __init setup_arch(char **cmdline_p) +{ + extern int mem_reserve(unsigned long, unsigned long, int); + extern void bootmem_init(void); + + memcpy(saved_command_line, command_line, COMMAND_LINE_SIZE); + saved_command_line[COMMAND_LINE_SIZE-1] = '\0'; + *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); + 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); + + bootmem_init(); + + platform_setup(cmdline_p); + + + paging_init(); + +#ifdef CONFIG_VT +# if defined(CONFIG_VGA_CONSOLE) + conswitchp = &vga_con; +# elif defined(CONFIG_DUMMY_CONSOLE) + conswitchp = &dummy_con; +# endif +#endif + +#if CONFIG_PCI + platform_pcibios_init(); +#endif +} + +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) +{ + /* high-level stuff */ + seq_printf(f,"processor\t: 0\n" + "vendor_id\t: Tensilica\n" + "model\t\t: Xtensa " XCHAL_HW_RELEASE_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", + XCHAL_BUILD_UNIQUE_ID, + XCHAL_HAVE_BE ? "big" : "little", + CCOUNT_PER_JIFFY/(1000000/HZ), + (CCOUNT_PER_JIFFY/(10000/HZ)) % 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 + "\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); + + /* Coprocessors */ +#if XCHAL_HAVE_CP + seq_printf(f, "coprocessors\t: %d\n", XCHAL_CP_NUM); +#else + seq_printf(f, "coprocessors\t: none\n"); +#endif + + /* {I,D}{RAM,ROM} and XLMI */ + seq_printf(f,"inst ROMs\t: %d\n" + "inst RAMs\t: %d\n" + "data ROMs\t: %d\n" + "data RAMs\t: %d\n" + "XLMI ports\t: %d\n", + XCHAL_NUM_IROM, + XCHAL_NUM_IRAM, + XCHAL_NUM_DROM, + XCHAL_NUM_DRAM, + XCHAL_NUM_XLMI); + + /* 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); + + /* MMU */ + seq_printf(f,"ASID bits\t: %d\n" + "ASID invalid\t: %d\n" + "ASID kernel\t: %d\n" + "rings\t\t: %d\n" + "itlb ways\t: %d\n" + "itlb AR ways\t: %d\n" + "dtlb ways\t: %d\n" + "dtlb AR ways\t: %d\n", + XCHAL_MMU_ASID_BITS, + XCHAL_MMU_ASID_INVALID, + XCHAL_MMU_ASID_KERNEL, + XCHAL_MMU_RINGS, + XCHAL_ITLB_WAYS, + XCHAL_ITLB_ARF_WAYS, + XCHAL_DTLB_WAYS, + XCHAL_DTLB_ARF_WAYS); + + return 0; +} + +/* + * We show only CPU #0 info. + */ +static void * +c_start(struct seq_file *f, loff_t *pos) +{ + return (void *) ((*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) +{ +} + +struct seq_operations cpuinfo_op = +{ + start: c_start, + next: c_next, + stop: c_stop, + show: c_show +}; + +#endif /* CONFIG_PROC_FS */ + diff --git a/arch/xtensa/kernel/signal.c b/arch/xtensa/kernel/signal.c new file mode 100644 index 0000000000..df6e1e17b0 --- /dev/null +++ b/arch/xtensa/kernel/signal.c @@ -0,0 +1,713 @@ +// TODO coprocessor stuff +/* + * linux/arch/xtensa/kernel/signal.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * 1997-11-28 Modified for POSIX.1b signals by Richard Henderson + * + * Joe Taylor + * Chris Zankel + * + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_SIG 0 + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +asmlinkage long sys_wait4(pid_t pid,unsigned int * stat_addr, int options, + struct rusage * ru); +asmlinkage int do_signal(struct pt_regs *regs, sigset_t *oldset); + +extern struct task_struct *coproc_owners[]; + + +/* + * Atomically swap in the new signal mask, and wait for a signal. + */ + +int sys_sigsuspend(struct pt_regs *regs) +{ + old_sigset_t mask = (old_sigset_t) regs->areg[3]; + sigset_t saveset; + + mask &= _BLOCKABLE; + spin_lock_irq(¤t->sighand->siglock); + saveset = current->blocked; + siginitset(¤t->blocked, mask); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + regs->areg[2] = -EINTR; + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + if (do_signal(regs, &saveset)) + return -EINTR; + } +} + +asmlinkage int +sys_rt_sigsuspend(struct pt_regs *regs) +{ + sigset_t *unewset = (sigset_t *) regs->areg[4]; + size_t sigsetsize = (size_t) regs->areg[3]; + sigset_t saveset, newset; + /* XXX: Don't preclude handling different sized sigset_t's. */ + if (sigsetsize != sizeof(sigset_t)) + return -EINVAL; + + if (copy_from_user(&newset, unewset, sizeof(newset))) + return -EFAULT; + sigdelsetmask(&newset, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + saveset = current->blocked; + current->blocked = newset; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + regs->areg[2] = -EINTR; + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + if (do_signal(regs, &saveset)) + return -EINTR; + } +} + +asmlinkage int +sys_sigaction(int sig, const struct old_sigaction *act, + struct old_sigaction *oact) +{ + struct k_sigaction new_ka, old_ka; + int ret; + + if (act) { + old_sigset_t mask; + if (verify_area(VERIFY_READ, act, sizeof(*act)) || + __get_user(new_ka.sa.sa_handler, &act->sa_handler) || + __get_user(new_ka.sa.sa_restorer, &act->sa_restorer)) + return -EFAULT; + __get_user(new_ka.sa.sa_flags, &act->sa_flags); + __get_user(mask, &act->sa_mask); + siginitset(&new_ka.sa.sa_mask, mask); + } + + ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL); + + if (!ret && oact) { + if (verify_area(VERIFY_WRITE, oact, sizeof(*oact)) || + __put_user(old_ka.sa.sa_handler, &oact->sa_handler) || + __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer)) + return -EFAULT; + __put_user(old_ka.sa.sa_flags, &oact->sa_flags); + __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); + } + + return ret; +} + +asmlinkage int +sys_sigaltstack(struct pt_regs *regs) +{ + const stack_t *uss = (stack_t *) regs->areg[4]; + stack_t *uoss = (stack_t *) regs->areg[3]; + + if (regs->depc > 64) + panic ("Double exception sys_sigreturn\n"); + + + return do_sigaltstack(uss, uoss, regs->areg[1]); +} + + +/* + * Do a signal return; undo the signal stack. + */ + +struct sigframe +{ + struct sigcontext sc; + struct _cpstate cpstate; + unsigned long extramask[_NSIG_WORDS-1]; + unsigned char retcode[6]; + unsigned int reserved[4]; /* Reserved area for chaining */ + unsigned int window[4]; /* Window of 4 registers for initial context */ +}; + +struct rt_sigframe +{ + struct siginfo info; + struct ucontext uc; + struct _cpstate cpstate; + unsigned char retcode[6]; + unsigned int reserved[4]; /* Reserved area for chaining */ + unsigned int window[4]; /* Window of 4 registers for initial context */ +}; + +extern void release_all_cp (struct task_struct *); + + +// FIXME restore_cpextra +static inline int +restore_cpextra (struct _cpstate *buf) +{ +#if 0 + /* 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. */ + + struct task_struct *tsk = current; + release_all_cp(tsk); + return __copy_from_user(tsk->thread.cpextra, buf, TOTAL_CPEXTRA_SIZE); +#endif + return 0; +} + +/* Note: We don't copy double exception 'tregs', 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 +restore_sigcontext(struct pt_regs *regs, struct sigcontext *sc) +{ + struct thread_struct *thread; + unsigned int err = 0; + unsigned long ps; + struct _cpstate *buf; + +#define COPY(x) err |= __get_user(regs->x, &sc->sc_##x) + COPY(pc); + COPY(depc); + COPY(wmask); + COPY(lbeg); + COPY(lend); + COPY(lcount); + COPY(sar); + COPY(windowbase); + COPY(windowstart); +#undef COPY + + /* 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 & ~XCHAL_PS_CALLINC_MASK) + | (ps & XCHAL_PS_CALLINC_MASK); + + /* Additional corruption checks */ + + if ((regs->windowbase >= (XCHAL_NUM_AREGS/4)) + || ((regs->windowstart & ~((1<<(XCHAL_NUM_AREGS/4)) - 1)) != 0) ) + err = 1; + if ((regs->lcount > 0) + && ((regs->lbeg > TASK_SIZE) || (regs->lend > TASK_SIZE)) ) + err = 1; + + /* Restore extended register state. + * See struct thread_struct in processor.h. + */ + thread = ¤t->thread; + + err |= __copy_from_user (regs->areg, sc->sc_areg, XCHAL_NUM_AREGS*4); + err |= __get_user(buf, &sc->sc_cpstate); + if (buf) { + if (verify_area(VERIFY_READ, buf, sizeof(*buf))) + goto badframe; + err |= restore_cpextra(buf); + } + + regs->syscall = -1; /* disable syscall checks */ + return err; + +badframe: + return 1; +} + +static inline void +flush_my_cpstate(struct task_struct *tsk) +{ + unsigned long flags; + local_irq_save(flags); + +#if 0 // FIXME + for (i = 0; i < XCHAL_CP_NUM; i++) { + if (tsk == coproc_owners[i]) { + xthal_validate_cp(i); + xthal_save_cpregs(tsk->thread.cpregs_ptr[i], i); + + /* Invalidate and "disown" the cp to allow + * callers the chance to reset cp state in the + * task_struct. */ + + xthal_invalidate_cp(i); + coproc_owners[i] = 0; + } + } +#endif + local_irq_restore(flags); +} + +/* Return codes: + 0: nothing saved + 1: stuff to save, successful + -1: stuff to save, error happened +*/ +static int +save_cpextra (struct _cpstate *buf) +{ +#if (XCHAL_EXTRA_SA_SIZE == 0) && (XCHAL_CP_NUM == 0) + return 0; +#else + + /* FIXME: If a task has never used a coprocessor, there is + * no need to save and restore anything. Tracking this + * information would allow us to optimize this section. + * Perhaps we can use current->used_math or (current->flags & + * PF_USEDFPU) or define a new field in the thread + * structure. */ + + /* We flush any live, task-owned cp state to the task_struct, + * then copy it all to the sigframe. Then we clear all + * cp/extra state in the task_struct, effectively + * clearing/resetting all cp/extra state for the signal + * handler (cp-exception handling will load these new values + * into the cp/extra registers.) This step is important for + * things like a floating-point cp, where the OS must reset + * the FCR to the default rounding mode. */ + + int err = 0; + struct task_struct *tsk = current; + + flush_my_cpstate(tsk); + /* Note that we just copy everything: 'extra' and 'cp' state together.*/ + err |= __copy_to_user(buf, tsk->thread.cp_save, XTENSA_CP_EXTRA_SIZE); + memset(tsk->thread.cp_save, 0, XTENSA_CP_EXTRA_SIZE); + +#if (XTENSA_CP_EXTRA_SIZE == 0) +#error Sanity check on memset above, cpextra_size should not be zero. +#endif + + return err ? -1 : 1; +#endif +} + +static int +setup_sigcontext(struct sigcontext *sc, struct _cpstate *cpstate, + struct pt_regs *regs, unsigned long mask) +{ + struct thread_struct *thread; + int err = 0; + +//printk("setup_sigcontext\n"); +#define COPY(x) err |= __put_user(regs->x, &sc->sc_##x) + COPY(pc); + COPY(ps); + COPY(depc); + COPY(wmask); + COPY(lbeg); + COPY(lend); + COPY(lcount); + COPY(sar); + COPY(windowbase); + COPY(windowstart); +#undef COPY + + /* Save extended register state. + * See struct thread_struct in processor.h. + */ + thread = ¤t->thread; + err |= __copy_to_user (sc->sc_areg, regs->areg, XCHAL_NUM_AREGS * 4); + err |= save_cpextra(cpstate); + err |= __put_user(err ? NULL : cpstate, &sc->sc_cpstate); + /* non-iBCS2 extensions.. */ + err |= __put_user(mask, &sc->oldmask); + + return err; +} + +asmlinkage int sys_sigreturn(struct pt_regs *regs) +{ + struct sigframe *frame = (struct sigframe *)regs->areg[1]; + sigset_t set; + if (regs->depc > 64) + panic ("Double exception sys_sigreturn\n"); + + if (verify_area(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + + if (__get_user(set.sig[0], &frame->sc.oldmask) + || (_NSIG_WORDS > 1 + && __copy_from_user(&set.sig[1], &frame->extramask, + sizeof(frame->extramask)))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->sc)) + goto badframe; + return regs->areg[2]; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +asmlinkage int sys_rt_sigreturn(struct pt_regs *regs) +{ + struct rt_sigframe *frame = (struct rt_sigframe *)regs->areg[1]; + sigset_t set; + stack_t st; + int ret; + if (regs->depc > 64) + { + printk("!!!!!!! DEPC !!!!!!!\n"); + return 0; + } + + if (verify_area(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + goto badframe; + ret = regs->areg[2]; + + if (__copy_from_user(&st, &frame->uc.uc_stack, sizeof(st))) + goto badframe; + /* It is more difficult to avoid calling this function than to + call it and ignore errors. */ + do_sigaltstack(&st, NULL, regs->areg[1]); + + return ret; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +/* + * Set up a signal frame. + */ + +/* + * Determine which stack to use.. + */ +static inline void * +get_sigframe(struct k_sigaction *ka, unsigned long sp, size_t frame_size) +{ + if ((ka->sa.sa_flags & SA_ONSTACK) != 0 && ! on_sig_stack(sp)) + sp = current->sas_ss_sp + current->sas_ss_size; + + return (void *)((sp - frame_size) & -16ul); +} + +#define USE_SIGRETURN 0 +#define USE_RT_SIGRETURN 1 + +static int +gen_return_code(unsigned char *codemem, unsigned int use_rt_sigreturn) +{ + unsigned int retcall; + int err = 0; + +#if 0 + /* Ignoring SA_RESTORER for now; it's supposed to be obsolete, + * and the xtensa glibc doesn't use it. + */ + if (ka->sa.sa_flags & SA_RESTORER) { + regs->pr = (unsigned long) ka->sa.sa_restorer; + } else +#endif /* 0 */ + { + +#if (__NR_sigreturn > 255) || (__NR_rt_sigreturn > 255) + +/* 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. + */ + +#error Generating the MOVI instruction below breaks! +#endif + + retcall = use_rt_sigreturn ? __NR_rt_sigreturn : __NR_sigreturn; + +#ifdef __XTENSA_EB__ /* Big Endian version */ + /* Generate instruction: MOVI a2, retcall */ + err |= __put_user(0x22, &codemem[0]); + err |= __put_user(0x0a, &codemem[1]); + err |= __put_user(retcall, &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, retcall */ + err |= __put_user(0x22, &codemem[0]); + err |= __put_user(0xa0, &codemem[1]); + err |= __put_user(retcall, &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) + __flush_invalidate_cache_range((unsigned long)codemem, 6UL); + + return err; +} + +static void +set_thread_state(struct pt_regs *regs, void *stack, unsigned char *retaddr, + void *handler, unsigned long arg1, void *arg2, void *arg3) +{ + /* Set up registers for signal handler */ + start_thread(regs, (unsigned long) handler, (unsigned long) stack); + + /* Set up a stack frame for a call4 + * Note: PS.CALLINC is set to one by start_thread + */ + regs->areg[4] = (((unsigned long) retaddr) & 0x3fffffff) | 0x40000000; + regs->areg[6] = arg1; + regs->areg[7] = (unsigned long) arg2; + regs->areg[8] = (unsigned long) arg3; +} + +static void setup_frame(int sig, struct k_sigaction *ka, + sigset_t *set, struct pt_regs *regs) +{ + struct sigframe *frame; + int err = 0; + int signal; + + frame = get_sigframe(ka, regs->areg[1], sizeof(*frame)); + if (regs->depc > 64) + { + printk("!!!!!!! DEPC !!!!!!!\n"); + return; + } + + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + signal = current_thread_info()->exec_domain + && current_thread_info()->exec_domain->signal_invmap + && sig < 32 + ? current_thread_info()->exec_domain->signal_invmap[sig] + : sig; + + err |= setup_sigcontext(&frame->sc, &frame->cpstate, regs, set->sig[0]); + + if (_NSIG_WORDS > 1) { + err |= __copy_to_user(frame->extramask, &set->sig[1], + sizeof(frame->extramask)); + } + + /* Create sys_sigreturn syscall in stack frame */ + err |= gen_return_code(frame->retcode, USE_SIGRETURN); + + if (err) + goto give_sigsegv; + + /* Create signal handler execution context. + * Return context not modified until this point. + */ + set_thread_state(regs, frame, frame->retcode, + ka->sa.sa_handler, signal, &frame->sc, NULL); + + /* 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 deliver (%s:%d): signal=%d sp=%p pc=%08x\n", + current->comm, current->pid, signal, frame, regs->pc); +#endif + + return; + +give_sigsegv: + if (sig == SIGSEGV) + ka->sa.sa_handler = SIG_DFL; + force_sig(SIGSEGV, current); +} + +static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info, + sigset_t *set, struct pt_regs *regs) +{ + struct rt_sigframe *frame; + int err = 0; + int signal; + + frame = get_sigframe(ka, regs->areg[1], sizeof(*frame)); + if (regs->depc > 64) + panic ("Double exception sys_sigreturn\n"); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + signal = current_thread_info()->exec_domain + && current_thread_info()->exec_domain->signal_invmap + && sig < 32 + ? current_thread_info()->exec_domain->signal_invmap[sig] + : sig; + + err |= copy_siginfo_to_user(&frame->info, info); + + /* Create the ucontext. */ + err |= __put_user(0, &frame->uc.uc_flags); + err |= __put_user(0, &frame->uc.uc_link); + err |= __put_user((void *)current->sas_ss_sp, + &frame->uc.uc_stack.ss_sp); + err |= __put_user(sas_ss_flags(regs->areg[1]), + &frame->uc.uc_stack.ss_flags); + err |= __put_user(current->sas_ss_size, &frame->uc.uc_stack.ss_size); + err |= setup_sigcontext(&frame->uc.uc_mcontext, &frame->cpstate, + regs, set->sig[0]); + err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + + /* Create sys_rt_sigreturn syscall in stack frame */ + err |= gen_return_code(frame->retcode, USE_RT_SIGRETURN); + + if (err) + goto give_sigsegv; + + /* Create signal handler execution context. + * Return context not modified until this point. + */ + set_thread_state(regs, frame, frame->retcode, + ka->sa.sa_handler, signal, &frame->info, &frame->uc); + + /* 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; + +give_sigsegv: + if (sig == SIGSEGV) + ka->sa.sa_handler = SIG_DFL; + force_sig(SIGSEGV, current); +} + + + +/* + * 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. + */ +int do_signal(struct pt_regs *regs, sigset_t *oldset) +{ + siginfo_t info; + int signr; + struct k_sigaction ka; + + if (!oldset) + oldset = ¤t->blocked; + + signr = get_signal_to_deliver(&info, &ka, regs, NULL); + + /* Are we from a system call? */ + if (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 (!(ka.sa.sa_flags & SA_RESTART)) { + regs->areg[2] = -EINTR; + break; + } + /* fallthrough */ + case ERESTARTNOINTR: + regs->areg[2] = regs->syscall; + regs->pc -= 3; + } + } + + if (signr == 0) + return 0; /* no signals delivered */ + + /* Whee! Actually deliver the signal. */ + + /* Set up the stack frame */ + if (ka.sa.sa_flags & SA_SIGINFO) + setup_rt_frame(signr, &ka, &info, oldset, regs); + else + setup_frame(signr, &ka, oldset, regs); + + if (ka.sa.sa_flags & SA_ONESHOT) + ka.sa.sa_handler = SIG_DFL; + + if (!(ka.sa.sa_flags & SA_NODEFER)) { + spin_lock_irq(¤t->sighand->siglock); + sigorsets(¤t->blocked, ¤t->blocked, &ka.sa.sa_mask); + sigaddset(¤t->blocked, signr); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + } + return 1; +} diff --git a/arch/xtensa/kernel/syscalls.c b/arch/xtensa/kernel/syscalls.c new file mode 100644 index 0000000000..abc8ed6c70 --- /dev/null +++ b/arch/xtensa/kernel/syscalls.c @@ -0,0 +1,418 @@ +/* + * 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 + * Marc Gauthier + * Chris Zankel + * Kevin Chea + * + */ + +#define DEBUG 0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern void do_syscall_trace(void); +typedef int (*syscall_t)(void *a0,...); +extern int (*do_syscalls)(struct pt_regs *regs, syscall_t fun, + int narg); +extern syscall_t sys_call_table[]; +extern unsigned char sys_narg_table[]; + +/* + * sys_pipe() is the normal C calling standard for creating a pipe. It's not + * the way unix traditional does this, though. + */ + +int sys_pipe(int __user *userfds) +{ + int fd[2]; + int error; + + error = do_pipe(fd); + if (!error) { + if (copy_to_user(userfds, fd, 2 * sizeof(int))) + error = -EFAULT; + } + return error; +} + +/* + * Common code for old and new mmaps. + */ + +static inline long do_mmap2(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + unsigned long fd, unsigned long pgoff) +{ + int error = -EBADF; + struct file * file = NULL; + + flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); + if (!(flags & MAP_ANONYMOUS)) { + file = fget(fd); + if (!file) + goto out; + } + + down_write(¤t->mm->mmap_sem); + error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); + up_write(¤t->mm->mmap_sem); + + if (file) + fput(file); +out: + return error; +} + +unsigned long old_mmap(unsigned long addr, size_t len, int prot, + int flags, int fd, off_t offset) +{ + return do_mmap2(addr, len, prot, flags, fd, offset >> PAGE_SHIFT); +} + +long sys_mmap2(unsigned long addr, unsigned long len, unsigned long prot, + unsigned long flags, unsigned long fd, unsigned long pgoff) +{ + return do_mmap2(addr, len, prot, flags, fd, pgoff); +} + +int sys_fork(struct pt_regs *regs) +{ + return do_fork(SIGCHLD, regs->areg[1], regs, 0, NULL, NULL); +} + +int sys_vfork(struct pt_regs *regs) +{ + return do_fork(CLONE_VFORK|CLONE_VM|SIGCHLD, regs->areg[1], + regs, 0, NULL, NULL); +} + +int sys_clone(struct pt_regs *regs) +{ + unsigned long clone_flags; + unsigned long newsp; + int __user *parent_tidptr, *child_tidptr; + clone_flags = regs->areg[4]; + newsp = regs->areg[3]; + parent_tidptr = (int __user *)regs->areg[5]; + child_tidptr = (int __user *)regs->areg[6]; + if (!newsp) + newsp = regs->areg[1]; + return do_fork(clone_flags,newsp,regs,0,parent_tidptr,child_tidptr); +} + +/* + * sys_execve() executes a new program. + */ + +int sys_execve(struct pt_regs *regs) +{ + int error; + char * filename; + + filename = getname((char *) (long)regs->areg[5]); + error = PTR_ERR(filename); + if (IS_ERR(filename)) + goto out; + error = do_execve(filename, (char **) (long)regs->areg[3], + (char **) (long)regs->areg[4], regs); + putname(filename); + +out: + return error; +} + +int sys_uname(struct old_utsname * name) +{ + if (name && !copy_to_user(name, &system_utsname, sizeof (*name))) + return 0; + return -EFAULT; +} + +int sys_olduname(struct oldold_utsname * name) +{ + int error; + + if (!name) + return -EFAULT; + if (!access_ok(VERIFY_WRITE,name,sizeof(struct oldold_utsname))) + return -EFAULT; + + error = __copy_to_user(&name->sysname,&system_utsname.sysname,__OLD_UTS_LEN); + error -= __put_user(0,name->sysname+__OLD_UTS_LEN); + error -= __copy_to_user(&name->nodename,&system_utsname.nodename,__OLD_UTS_LEN); + error -= __put_user(0,name->nodename+__OLD_UTS_LEN); + error -= __copy_to_user(&name->release,&system_utsname.release,__OLD_UTS_LEN); + error -= __put_user(0,name->release+__OLD_UTS_LEN); + error -= __copy_to_user(&name->version,&system_utsname.version,__OLD_UTS_LEN); + error -= __put_user(0,name->version+__OLD_UTS_LEN); + error -= __copy_to_user(&name->machine,&system_utsname.machine,__OLD_UTS_LEN); + error -= __put_user(0,name->machine+__OLD_UTS_LEN); + + return error ? -EFAULT : 0; +} + + +/* + * Build the string table for the builtin "poor man's strace". + */ + +#if DEBUG +#define SYSCALL(fun, narg) #fun, +static char *sfnames[] = { +#include "syscalls.h" +}; +#undef SYS +#endif + +void system_call (struct pt_regs *regs) +{ + syscall_t syscall; + unsigned long parm0, parm1, parm2, parm3, parm4, parm5; + int nargs, res; + unsigned int syscallnr; + int ps; + +#if DEBUG + int i; + unsigned long parms[6]; + char *sysname; +#endif + + regs->syscall = regs->areg[2]; + + do_syscall_trace(); + + /* Have to load after syscall_trace because strace + * sometimes changes regs->syscall. + */ + syscallnr = regs->syscall; + + parm0 = parm1 = parm2 = parm3 = parm4 = parm5 = 0; + + /* Restore interrupt level to syscall invoker's. + * If this were in assembly, we wouldn't disable + * interrupts in the first place: + */ + local_save_flags (ps); + local_irq_restore((ps & ~XCHAL_PS_INTLEVEL_MASK) | + (regs->ps & XCHAL_PS_INTLEVEL_MASK) ); + + if (syscallnr > __NR_Linux_syscalls) { + regs->areg[2] = -ENOSYS; + return; + } + + syscall = sys_call_table[syscallnr]; + nargs = sys_narg_table[syscallnr]; + + if (syscall == NULL) { + regs->areg[2] = -ENOSYS; + return; + } + + /* There shouldn't be more than six arguments in the table! */ + + if (nargs > 6) + panic("Internal error - too many syscall arguments (%d)!\n", + nargs); + + /* Linux takes system-call arguments in registers. The ABI + * and Xtensa software conventions require the system-call + * number in a2. If an argument exists in a2, we move it to + * the next available register. Note that for improved + * efficiency, we do NOT shift all parameters down one + * register to maintain the original order. + * + * At best case (zero arguments), we just write the syscall + * number to a2. At worst case (1 to 6 arguments), we move + * the argument in a2 to the next available register, then + * write the syscall number to a2. + * + * For clarity, the following truth table enumerates all + * possibilities. + * + * arguments syscall number arg0, arg1, arg2, arg3, arg4, arg5 + * --------- -------------- ---------------------------------- + * 0 a2 + * 1 a2 a3 + * 2 a2 a4, a3 + * 3 a2 a5, a3, a4 + * 4 a2 a6, a3, a4, a5 + * 5 a2 a7, a3, a4, a5, a6 + * 6 a2 a8, a3, a4, a5, a6, a7 + */ + if (nargs) { + parm0 = regs->areg[nargs+2]; + parm1 = regs->areg[3]; + parm2 = regs->areg[4]; + parm3 = regs->areg[5]; + parm4 = regs->areg[6]; + parm5 = regs->areg[7]; + } else /* nargs == 0 */ + parm0 = (unsigned long) regs; + +#if DEBUG + parms[0] = parm0; + parms[1] = parm1; + parms[2] = parm2; + parms[3] = parm3; + parms[4] = parm4; + parms[5] = parm5; + + sysname = sfnames[syscallnr]; + if (strncmp(sysname, "sys_", 4) == 0) + sysname = sysname + 4; + + printk("\017SYSCALL:I:%x:%d:%s %s(", regs->pc, current->pid, + current->comm, sysname); + for (i = 0; i < nargs; i++) + printk((i>0) ? ", %#lx" : "%#lx", parms[i]); + printk(")\n"); +#endif + + res = syscall((void *)parm0, parm1, parm2, parm3, parm4, parm5); + +#if DEBUG + printk("\017SYSCALL:O:%d:%s %s(",current->pid, current->comm, sysname); + for (i = 0; i < nargs; i++) + printk((i>0) ? ", %#lx" : "%#lx", parms[i]); + if (res < 4096) + printk(") = %d\n", res); + else + printk(") = %#x\n", res); +#endif /* DEBUG */ + + regs->areg[2] = res; + do_syscall_trace(); +} + +/* + * sys_ipc() is the de-multiplexer for the SysV IPC calls.. + * + * This is really horribly ugly. + */ + +int sys_ipc (uint call, int first, int second, + int third, void __user *ptr, long fifth) +{ + int version, ret; + + version = call >> 16; /* hack for backward compatibility */ + call &= 0xffff; + ret = -ENOSYS; + + switch (call) { + case SEMOP: + ret = sys_semtimedop (first, (struct sembuf __user *)ptr, + second, NULL); + break; + + case SEMTIMEDOP: + ret = sys_semtimedop (first, (struct sembuf __user *)ptr, + second, (const struct timespec *) fifth); + break; + + case SEMGET: + ret = sys_semget (first, second, third); + break; + + case SEMCTL: { + union semun fourth; + + if (ptr && !get_user(fourth.__pad, (void *__user *) ptr)) + ret = sys_semctl (first, second, third, fourth); + break; + } + + case MSGSND: + ret = sys_msgsnd (first, (struct msgbuf __user*) ptr, + second, third); + break; + + case MSGRCV: + switch (version) { + case 0: { + struct ipc_kludge tmp; + + if (ptr && !copy_from_user(&tmp, + (struct ipc_kludge *) ptr, + sizeof (tmp))) + ret = sys_msgrcv (first, tmp.msgp, second, + tmp.msgtyp, third); + break; + } + + default: + ret = sys_msgrcv (first, (struct msgbuf __user *) ptr, + second, 0, third); + break; + } + break; + + case MSGGET: + ret = sys_msgget ((key_t) first, second); + break; + + case MSGCTL: + ret = sys_msgctl (first, second, (struct msqid_ds __user*) ptr); + break; + + case SHMAT: { + ulong raddr; + ret = do_shmat (first, (char __user *) ptr, second, &raddr); + + if (!ret) + ret = put_user (raddr, (ulong __user *) third); + + break; + } + + case SHMDT: + ret = sys_shmdt ((char __user *)ptr); + break; + + case SHMGET: + ret = sys_shmget (first, second, third); + break; + + case SHMCTL: + ret = sys_shmctl (first, second, (struct shmid_ds __user*) ptr); + break; + } + return ret; +} + diff --git a/arch/xtensa/kernel/syscalls.h b/arch/xtensa/kernel/syscalls.h new file mode 100644 index 0000000000..5b3f75f50f --- /dev/null +++ b/arch/xtensa/kernel/syscalls.h @@ -0,0 +1,248 @@ +/* + * arch/xtensa/kernel/syscalls.h + * + * 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, 1996, 1997, 1998 by Ralf Baechle + * Copyright (C) 2001 - 2005 Tensilica Inc. + * + * Changes by Joe Taylor + */ + +/* + * This file is being included twice - once to build a list of all + * syscalls and once to build a table of how many arguments each syscall + * accepts. Syscalls that receive a pointer to the saved registers are + * marked as having zero arguments. + * + * The binary compatibility calls are in a separate list. + * + * Entry '0' used to be system_call. It's removed to disable indirect + * system calls for now so user tasks can't recurse. See mips' + * sys_syscall for a comparable example. + */ + +SYSCALL(0, 0) /* 00 */ + +SYSCALL(sys_exit, 1) +SYSCALL(sys_fork, 0) +SYSCALL(sys_read, 3) +SYSCALL(sys_write, 3) +SYSCALL(sys_open, 3) /* 05 */ +SYSCALL(sys_close, 1) +SYSCALL(sys_waitpid, 3) +SYSCALL(sys_creat, 2) +SYSCALL(sys_link, 2) +SYSCALL(sys_unlink, 1) /* 10 */ +SYSCALL(sys_execve, 0) +SYSCALL(sys_chdir, 1) +SYSCALL(sys_time, 1) +SYSCALL(sys_mknod, 3) +SYSCALL(sys_chmod, 2) /* 15 */ +SYSCALL(sys_lchown, 3) +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_stat, 2) +SYSCALL(sys_lseek, 3) +SYSCALL(sys_getpid, 0) /* 20 */ +SYSCALL(sys_mount, 5) +SYSCALL(sys_oldumount, 1) +SYSCALL(sys_setuid, 1) +SYSCALL(sys_getuid, 0) +SYSCALL(sys_stime, 1) /* 25 */ +SYSCALL(sys_ptrace, 4) +SYSCALL(sys_alarm, 1) +SYSCALL(sys_fstat, 2) +SYSCALL(sys_pause, 0) +SYSCALL(sys_utime, 2) /* 30 */ +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_access, 2) +SYSCALL(sys_nice, 1) +SYSCALL(sys_ni_syscall, 0) /* 35 */ +SYSCALL(sys_sync, 0) +SYSCALL(sys_kill, 2) +SYSCALL(sys_rename, 2) +SYSCALL(sys_mkdir, 2) +SYSCALL(sys_rmdir, 1) /* 40 */ +SYSCALL(sys_dup, 1) +SYSCALL(sys_pipe, 1) +SYSCALL(sys_times, 1) +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_brk, 1) /* 45 */ +SYSCALL(sys_setgid, 1) +SYSCALL(sys_getgid, 0) +SYSCALL(sys_ni_syscall, 0) /* was signal(2) */ +SYSCALL(sys_geteuid, 0) +SYSCALL(sys_getegid, 0) /* 50 */ +SYSCALL(sys_acct, 1) +SYSCALL(sys_umount, 2) +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_ioctl, 3) +SYSCALL(sys_fcntl, 3) /* 55 */ +SYSCALL(sys_ni_syscall, 2) +SYSCALL(sys_setpgid, 2) +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_olduname, 1) +SYSCALL(sys_umask, 1) /* 60 */ +SYSCALL(sys_chroot, 1) +SYSCALL(sys_ustat, 2) +SYSCALL(sys_dup2, 2) +SYSCALL(sys_getppid, 0) +SYSCALL(sys_getpgrp, 0) /* 65 */ +SYSCALL(sys_setsid, 0) +SYSCALL(sys_sigaction, 3) +SYSCALL(sys_sgetmask, 0) +SYSCALL(sys_ssetmask, 1) +SYSCALL(sys_setreuid, 2) /* 70 */ +SYSCALL(sys_setregid, 2) +SYSCALL(sys_sigsuspend, 0) +SYSCALL(sys_sigpending, 1) +SYSCALL(sys_sethostname, 2) +SYSCALL(sys_setrlimit, 2) /* 75 */ +SYSCALL(sys_getrlimit, 2) +SYSCALL(sys_getrusage, 2) +SYSCALL(sys_gettimeofday, 2) +SYSCALL(sys_settimeofday, 2) +SYSCALL(sys_getgroups, 2) /* 80 */ +SYSCALL(sys_setgroups, 2) +SYSCALL(sys_ni_syscall, 0) /* old_select */ +SYSCALL(sys_symlink, 2) +SYSCALL(sys_lstat, 2) +SYSCALL(sys_readlink, 3) /* 85 */ +SYSCALL(sys_uselib, 1) +SYSCALL(sys_swapon, 2) +SYSCALL(sys_reboot, 3) +SYSCALL(old_readdir, 3) +SYSCALL(old_mmap, 6) /* 90 */ +SYSCALL(sys_munmap, 2) +SYSCALL(sys_truncate, 2) +SYSCALL(sys_ftruncate, 2) +SYSCALL(sys_fchmod, 2) +SYSCALL(sys_fchown, 3) /* 95 */ +SYSCALL(sys_getpriority, 2) +SYSCALL(sys_setpriority, 3) +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_statfs, 2) +SYSCALL(sys_fstatfs, 2) /* 100 */ +SYSCALL(sys_ni_syscall, 3) +SYSCALL(sys_socketcall, 2) +SYSCALL(sys_syslog, 3) +SYSCALL(sys_setitimer, 3) +SYSCALL(sys_getitimer, 2) /* 105 */ +SYSCALL(sys_newstat, 2) +SYSCALL(sys_newlstat, 2) +SYSCALL(sys_newfstat, 2) +SYSCALL(sys_uname, 1) +SYSCALL(sys_ni_syscall, 0) /* 110 */ +SYSCALL(sys_vhangup, 0) +SYSCALL(sys_ni_syscall, 0) /* was sys_idle() */ +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_wait4, 4) +SYSCALL(sys_swapoff, 1) /* 115 */ +SYSCALL(sys_sysinfo, 1) +SYSCALL(sys_ipc, 5) /* 6 really, but glibc uses only 5) */ +SYSCALL(sys_fsync, 1) +SYSCALL(sys_sigreturn, 0) +SYSCALL(sys_clone, 0) /* 120 */ +SYSCALL(sys_setdomainname, 2) +SYSCALL(sys_newuname, 1) +SYSCALL(sys_ni_syscall, 0) /* sys_modify_ldt */ +SYSCALL(sys_adjtimex, 1) +SYSCALL(sys_mprotect, 3) /* 125 */ +SYSCALL(sys_sigprocmask, 3) +SYSCALL(sys_ni_syscall, 2) /* old sys_create_module */ +SYSCALL(sys_init_module, 2) +SYSCALL(sys_delete_module, 1) +SYSCALL(sys_ni_syscall, 1) /* old sys_get_kernel_sysm */ /* 130 */ +SYSCALL(sys_quotactl, 0) +SYSCALL(sys_getpgid, 1) +SYSCALL(sys_fchdir, 1) +SYSCALL(sys_bdflush, 2) +SYSCALL(sys_sysfs, 3) /* 135 */ +SYSCALL(sys_personality, 1) +SYSCALL(sys_ni_syscall, 0) /* for afs_syscall */ +SYSCALL(sys_setfsuid, 1) +SYSCALL(sys_setfsgid, 1) +SYSCALL(sys_llseek, 5) /* 140 */ +SYSCALL(sys_getdents, 3) +SYSCALL(sys_select, 5) +SYSCALL(sys_flock, 2) +SYSCALL(sys_msync, 3) +SYSCALL(sys_readv, 3) /* 145 */ +SYSCALL(sys_writev, 3) +SYSCALL(sys_ni_syscall, 3) +SYSCALL(sys_ni_syscall, 3) +SYSCALL(sys_ni_syscall, 4) /* handled in fast syscall handler. */ +SYSCALL(sys_ni_syscall, 0) /* 150 */ +SYSCALL(sys_getsid, 1) +SYSCALL(sys_fdatasync, 1) +SYSCALL(sys_sysctl, 1) +SYSCALL(sys_mlock, 2) +SYSCALL(sys_munlock, 2) /* 155 */ +SYSCALL(sys_mlockall, 1) +SYSCALL(sys_munlockall, 0) +SYSCALL(sys_sched_setparam,2) +SYSCALL(sys_sched_getparam,2) +SYSCALL(sys_sched_setscheduler,3) /* 160 */ +SYSCALL(sys_sched_getscheduler,1) +SYSCALL(sys_sched_yield,0) +SYSCALL(sys_sched_get_priority_max,1) +SYSCALL(sys_sched_get_priority_min,1) +SYSCALL(sys_sched_rr_get_interval,2) /* 165 */ +SYSCALL(sys_nanosleep,2) +SYSCALL(sys_mremap,4) +SYSCALL(sys_accept, 3) +SYSCALL(sys_bind, 3) +SYSCALL(sys_connect, 3) /* 170 */ +SYSCALL(sys_getpeername, 3) +SYSCALL(sys_getsockname, 3) +SYSCALL(sys_getsockopt, 5) +SYSCALL(sys_listen, 2) +SYSCALL(sys_recv, 4) /* 175 */ +SYSCALL(sys_recvfrom, 6) +SYSCALL(sys_recvmsg, 3) +SYSCALL(sys_send, 4) +SYSCALL(sys_sendmsg, 3) +SYSCALL(sys_sendto, 6) /* 180 */ +SYSCALL(sys_setsockopt, 5) +SYSCALL(sys_shutdown, 2) +SYSCALL(sys_socket, 3) +SYSCALL(sys_socketpair, 4) +SYSCALL(sys_setresuid, 3) /* 185 */ +SYSCALL(sys_getresuid, 3) +SYSCALL(sys_ni_syscall, 5) /* old sys_query_module */ +SYSCALL(sys_poll, 3) +SYSCALL(sys_nfsservctl, 3) +SYSCALL(sys_setresgid, 3) /* 190 */ +SYSCALL(sys_getresgid, 3) +SYSCALL(sys_prctl, 5) +SYSCALL(sys_rt_sigreturn, 0) +SYSCALL(sys_rt_sigaction, 4) +SYSCALL(sys_rt_sigprocmask, 4) /* 195 */ +SYSCALL(sys_rt_sigpending, 2) +SYSCALL(sys_rt_sigtimedwait, 4) +SYSCALL(sys_rt_sigqueueinfo, 3) +SYSCALL(sys_rt_sigsuspend, 0) +SYSCALL(sys_pread64, 5) /* 200 */ +SYSCALL(sys_pwrite64, 5) +SYSCALL(sys_chown, 3) +SYSCALL(sys_getcwd, 2) +SYSCALL(sys_capget, 2) +SYSCALL(sys_capset, 2) /* 205 */ +SYSCALL(sys_sigaltstack, 0) +SYSCALL(sys_sendfile, 4) +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_ni_syscall, 0) +SYSCALL(sys_mmap2, 6) /* 210 */ +SYSCALL(sys_truncate64, 2) +SYSCALL(sys_ftruncate64, 2) +SYSCALL(sys_stat64, 2) +SYSCALL(sys_lstat64, 2) +SYSCALL(sys_fstat64, 2) /* 215 */ +SYSCALL(sys_pivot_root, 2) +SYSCALL(sys_mincore, 3) +SYSCALL(sys_madvise, 3) +SYSCALL(sys_getdents64, 3) +SYSCALL(sys_vfork, 0) /* 220 */ diff --git a/arch/xtensa/kernel/time.c b/arch/xtensa/kernel/time.c new file mode 100644 index 0000000000..e07287db5a --- /dev/null +++ b/arch/xtensa/kernel/time.c @@ -0,0 +1,227 @@ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +extern volatile unsigned long wall_jiffies; + +u64 jiffies_64 = INITIAL_JIFFIES; +EXPORT_SYMBOL(jiffies_64); + +spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED; +EXPORT_SYMBOL(rtc_lock); + + +#ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT +unsigned long ccount_per_jiffy; /* per 1/HZ */ +unsigned long ccount_nsec; /* nsec per ccount increment */ +#endif + +unsigned int last_ccount_stamp; +static long last_rtc_update = 0; + +/* + * Scheduler clock - returns current tim in nanosec units. + */ + +unsigned long long sched_clock(void) +{ + return (unsigned long long)jiffies * (1000000000 / HZ); +} + +static irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static struct irqaction timer_irqaction = { + .handler = timer_interrupt, + .flags = SA_INTERRUPT, + .name = "timer", +}; + +void __init time_init(void) +{ + time_t sec_o, sec_n = 0; + + /* The platform must provide a function to calibrate the processor + * speed for the CALIBRATE. + */ + +#if CONFIG_XTENSA_CALIBRATE_CCOUNT + printk("Calibrating CPU frequency "); + platform_calibrate_ccount(); + printk("%d.%02d MHz\n", (int)ccount_per_jiffy/(1000000/HZ), + (int)(ccount_per_jiffy/(10000/HZ))%100); +#endif + + /* Set time from RTC (if provided) */ + + if (platform_get_rtc_time(&sec_o) == 0) + while (platform_get_rtc_time(&sec_n)) + if (sec_o != sec_n) + break; + + xtime.tv_nsec = 0; + last_rtc_update = xtime.tv_sec = sec_n; + last_ccount_stamp = get_ccount(); + + set_normalized_timespec(&wall_to_monotonic, + -xtime.tv_sec, -xtime.tv_nsec); + + /* Initialize the linux timer interrupt. */ + + setup_irq(LINUX_TIMER_INT, &timer_irqaction); + set_linux_timer(get_ccount() + CCOUNT_PER_JIFFY); +} + + +int do_settimeofday(struct timespec *tv) +{ + time_t wtm_sec, sec = tv->tv_sec; + long wtm_nsec, nsec = tv->tv_nsec; + unsigned long ccount; + + if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + + write_seqlock_irq(&xtime_lock); + + /* This is revolting. We need to set "xtime" correctly. However, the + * value in this location is the value at the most recent update of + * wall time. Discover what correction gettimeofday() would have + * made, and then undo it! + */ + ccount = get_ccount(); + nsec -= (ccount - last_ccount_stamp) * CCOUNT_NSEC; + nsec -= (jiffies - wall_jiffies) * CCOUNT_PER_JIFFY * CCOUNT_NSEC; + + wtm_sec = wall_to_monotonic.tv_sec + (xtime.tv_sec - sec); + wtm_nsec = wall_to_monotonic.tv_nsec + (xtime.tv_nsec - nsec); + + set_normalized_timespec(&xtime, sec, nsec); + set_normalized_timespec(&wall_to_monotonic, wtm_sec, wtm_nsec); + + time_adjust = 0; /* stop active adjtime() */ + time_status |= STA_UNSYNC; + time_maxerror = NTP_PHASE_LIMIT; + time_esterror = NTP_PHASE_LIMIT; + write_sequnlock_irq(&xtime_lock); + return 0; +} + +EXPORT_SYMBOL(do_settimeofday); + + +void do_gettimeofday(struct timeval *tv) +{ + unsigned long flags; + unsigned long sec, usec, delta, lost, seq; + + do { + seq = read_seqbegin_irqsave(&xtime_lock, flags); + + delta = get_ccount() - last_ccount_stamp; + sec = xtime.tv_sec; + usec = (xtime.tv_nsec / NSEC_PER_USEC); + + lost = jiffies - wall_jiffies; + + } while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); + + usec += lost * (1000000UL/HZ) + (delta * CCOUNT_NSEC) / NSEC_PER_USEC; + for (; usec >= 1000000; sec++, usec -= 1000000) + ; + + tv->tv_sec = sec; + tv->tv_usec = usec; +} + +EXPORT_SYMBOL(do_gettimeofday); + +/* + * The timer interrupt is called HZ times per second. + */ + +irqreturn_t timer_interrupt (int irq, void *dev_id, struct pt_regs *regs) +{ + + unsigned long next; + + next = get_linux_timer(); + +again: + while ((signed long)(get_ccount() - next) > 0) { + + profile_tick(CPU_PROFILING, regs); +#ifndef CONFIG_SMP + update_process_times(user_mode(regs)); +#endif + + write_seqlock(&xtime_lock); + + last_ccount_stamp = next; + next += CCOUNT_PER_JIFFY; + do_timer (regs); /* Linux handler in kernel/timer.c */ + + if ((time_status & STA_UNSYNC) == 0 && + xtime.tv_sec - last_rtc_update >= 659 && + abs((xtime.tv_nsec/1000)-(1000000-1000000/HZ))<5000000/HZ && + jiffies - wall_jiffies == 1) { + + if (platform_set_rtc_time(xtime.tv_sec+1) == 0) + last_rtc_update = xtime.tv_sec+1; + else + /* Do it again in 60 s */ + last_rtc_update += 60; + } + write_sequnlock(&xtime_lock); + } + + /* NOTE: writing CCOMPAREn clears the interrupt. */ + + set_linux_timer (next); + + /* Make sure we didn't miss any tick... */ + + if ((signed long)(get_ccount() - next) > 0) + goto again; + + /* Allow platform to do something usefull (Wdog). */ + + platform_heartbeat(); + + return IRQ_HANDLED; +} + +#ifndef CONFIG_GENERIC_CALIBRATE_DELAY +void __devinit calibrate_delay(void) +{ + loops_per_jiffy = CCOUNT_PER_JIFFY; + printk("Calibrating delay loop (skipped)... " + "%lu.%02lu BogoMIPS preset\n", + loops_per_jiffy/(1000000/HZ), + (loops_per_jiffy/(10000/HZ)) % 100); +} +#endif + diff --git a/arch/xtensa/kernel/traps.c b/arch/xtensa/kernel/traps.c new file mode 100644 index 0000000000..804246e743 --- /dev/null +++ b/arch/xtensa/kernel/traps.c @@ -0,0 +1,498 @@ +/* + * 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 - 2005 Tensilica Inc. + * + * Joe Taylor + * Chris Zankel + * Marc Gauthier + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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) \ +{ XCHAL_EXCCAUSE_COPROCESSOR ## x ## _DISABLED, USER, fast_coprocessor } + +typedef struct { + int cause; + int fast; + void* handler; +} dispatch_init_table_t; + +dispatch_init_table_t __init dispatch_init_table[] = { + +{ XCHAL_EXCCAUSE_ILLEGAL_INSTRUCTION, 0, do_illegal_instruction}, +{ XCHAL_EXCCAUSE_SYSTEM_CALL, KRNL, fast_syscall_kernel }, +{ XCHAL_EXCCAUSE_SYSTEM_CALL, USER, fast_syscall_user }, +{ XCHAL_EXCCAUSE_SYSTEM_CALL, 0, system_call }, +/* XCHAL_EXCCAUSE_INSTRUCTION_FETCH unhandled */ +/* XCHAL_EXCCAUSE_LOAD_STORE_ERROR unhandled*/ +{ XCHAL_EXCCAUSE_LEVEL1_INTERRUPT, 0, do_interrupt }, +{ XCHAL_EXCCAUSE_ALLOCA, USER|KRNL, fast_alloca }, +/* XCHAL_EXCCAUSE_INTEGER_DIVIDE_BY_ZERO unhandled */ +/* XCHAL_EXCCAUSE_PRIVILEGED unhandled */ +#if XCHAL_UNALIGNED_LOAD_EXCEPTION || XCHAL_UNALIGNED_STORE_EXCEPTION +#ifdef CONFIG_UNALIGNED_USER +{ XCHAL_EXCCAUSE_UNALIGNED, USER, fast_unaligned }, +#else +{ XCHAL_EXCCAUSE_UNALIGNED, 0, do_unaligned_user }, +#endif +{ XCHAL_EXCCAUSE_UNALIGNED, KRNL, fast_unaligned }, +#endif +{ XCHAL_EXCCAUSE_ITLB_MISS, 0, do_page_fault }, +{ XCHAL_EXCCAUSE_ITLB_MISS, USER|KRNL, fast_second_level_miss}, +{ XCHAL_EXCCAUSE_ITLB_MULTIHIT, 0, do_multihit }, +{ XCHAL_EXCCAUSE_ITLB_PRIVILEGE, 0, do_page_fault }, +/* XCHAL_EXCCAUSE_SIZE_RESTRICTION unhandled */ +{ XCHAL_EXCCAUSE_FETCH_CACHE_ATTRIBUTE, 0, do_page_fault }, +{ XCHAL_EXCCAUSE_DTLB_MISS, USER|KRNL, fast_second_level_miss}, +{ XCHAL_EXCCAUSE_DTLB_MISS, 0, do_page_fault }, +{ XCHAL_EXCCAUSE_DTLB_MULTIHIT, 0, do_multihit }, +{ XCHAL_EXCCAUSE_DTLB_PRIVILEGE, 0, do_page_fault }, +/* XCHAL_EXCCAUSE_DTLB_SIZE_RESTRICTION unhandled */ +{ XCHAL_EXCCAUSE_STORE_CACHE_ATTRIBUTE, USER|KRNL, fast_store_prohibited }, +{ XCHAL_EXCCAUSE_STORE_CACHE_ATTRIBUTE, 0, do_page_fault }, +{ XCHAL_EXCCAUSE_LOAD_CACHE_ATTRIBUTE, 0, do_page_fault }, +/* XCCHAL_EXCCAUSE_FLOATING_POINT unhandled */ +#if (XCHAL_CP_MASK & 1) +COPROCESSOR(0), +#endif +#if (XCHAL_CP_MASK & 2) +COPROCESSOR(1), +#endif +#if (XCHAL_CP_MASK & 4) +COPROCESSOR(2), +#endif +#if (XCHAL_CP_MASK & 8) +COPROCESSOR(3), +#endif +#if (XCHAL_CP_MASK & 16) +COPROCESSOR(4), +#endif +#if (XCHAL_CP_MASK & 32) +COPROCESSOR(5), +#endif +#if (XCHAL_CP_MASK & 64) +COPROCESSOR(6), +#endif +#if (XCHAL_CP_MASK & 128) +COPROCESSOR(7), +#endif +{ EXCCAUSE_MAPPED_DEBUG, 0, do_debug }, +{ -1, -1, 0 } + +}; + +/* The exception 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. + */ + +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, current->pid, 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); +} + +/* + * Level-1 interrupt. + * We currently have no priority encoding. + */ + +unsigned long ignored_level1_interrupts; +extern void do_IRQ(int, struct pt_regs *); + +void do_interrupt (struct pt_regs *regs) +{ + unsigned long intread = get_sr (INTREAD); + unsigned long intenable = get_sr (INTENABLE); + int i, mask; + + /* Handle all interrupts (no priorities). + * (Clear the interrupt before processing, in case it's + * edge-triggered or software-generated) + */ + + for (i=0, mask = 1; i < XCHAL_NUM_INTERRUPTS; i++, mask <<= 1) { + if (mask & (intread & intenable)) { + set_sr (mask, INTCLEAR); + do_IRQ (i,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, current->pid, 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 +#ifndef CONFIG_UNALIGNED_USER +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, current->pid, 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 +#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); +} + + +/* + * 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. + */ + +#define set_handler(idx,handler) (exc_table[idx] = (unsigned long) (handler)) + +void 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. */ + + i = (unsigned long)exc_table; + __asm__ __volatile__("wsr %0, "__stringify(EXCSAVE_1)"\n" : : "a" (i)); +} + +/* + * This function dumps the current valid window frame and other base registers. + */ + +void show_regs(struct pt_regs * regs) +{ + int i, wmask; + + wmask = regs->wmask & ~1; + + for (i = 0; i < 32; i++) { + if (wmask & (1 << (i / 4))) + break; + if ((i % 8) == 0) + printk ("\n" KERN_INFO "a%02d: ", i); + printk("%08lx ", regs->areg[i]); + } + printk("\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); +} + +void show_trace(struct task_struct *task, unsigned long *sp) +{ + unsigned long a0, a1, pc; + unsigned long sp_start, sp_end; + + a1 = (unsigned long)sp; + + if (a1 == 0) + __asm__ __volatile__ ("mov %0, a1\n" : "=a"(a1)); + + + sp_start = a1 & ~(THREAD_SIZE-1); + sp_end = sp_start + THREAD_SIZE; + + printk("Call Trace:"); +#ifdef CONFIG_KALLSYMS + printk("\n"); +#endif + spill_registers(); + + while (a1 > sp_start && a1 < sp_end) { + sp = (unsigned long*)a1; + + a0 = *(sp - 4); + a1 = *(sp - 3); + + if (a1 <= (unsigned long) sp) + break; + + pc = MAKE_PC_FROM_RA(a0, a1); + + if (kernel_text_address(pc)) { + printk(" [<%08lx>] ", pc); + print_symbol("%s\n", pc); + } + } + 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 == 0) + __asm__ __volatile__ ("mov %0, a1\n" : "=a"(sp)); + + 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 dump_stack(void) +{ + show_stack(current, NULL); +} + +EXPORT_SYMBOL(dump_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?' ':'>')); + } +} + +spinlock_t die_lock = SPIN_LOCK_UNLOCKED; + +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]); + + spin_unlock_irq(&die_lock); + + if (in_interrupt()) + panic("Fatal exception in interrupt"); + + if (panic_on_oops) { + printk(KERN_EMERG "Fatal exception: panic in 5 seconds\n"); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(5 * HZ); + panic("Fatal exception"); + } + do_exit(err); +} + + diff --git a/arch/xtensa/kernel/vectors.S b/arch/xtensa/kernel/vectors.S new file mode 100644 index 0000000000..81808f0c67 --- /dev/null +++ b/arch/xtensa/kernel/vectors.S @@ -0,0 +1,464 @@ +/* + * 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 Tensilica, Inc. + * + * Chris Zankel + * + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * 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, EXCSAVE_1 # 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 + jx a0 + +/* + * 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, EXCSAVE_1 # 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 + jx a0 + + +/* + * 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_A2 + * 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 + +ENTRY(_DoubleExceptionVector) + + /* Deliberately destroy excsave (don't assume it's value was valid). */ + + wsr a3, EXCSAVE_1 # save a3 + + /* Check for kernel double exception (usually fatal). */ + + rsr a3, PS + _bbci.l a3, PS_UM_SHIFT, .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 a3, XCHAL_WINDOW_VECTORS_VADDR + _bltu a0, a3, .Lfixup + addi a3, a3, XSHAL_WINDOW_VECTORS_SIZE + _bgeu a0, a3, .Lfixup + + /* Window overflow/underflow exception. Get stack pointer. */ + + mov a3, a2 + movi a2, exc_table + l32i a2, a2, EXC_TABLE_KSTK + + /* Check for overflow/underflow exception, jump if overflow. */ + + _bbci.l a0, 6, .Lovfl + + /* a0: depc, a1: a1, a2: kstk, a3: a2, depc: a0, excsave: a3 */ + + /* Restart window underflow exception. + * 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! + */ + + wsr a2, DEPC # save stack pointer temporarily + rsr a0, PS + extui a0, a0, XCHAL_PS_OWB_SHIFT, XCHAL_PS_OWB_BITS + 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 + + wsr a3, EXCSAVE_1 # save a3 + movi a3, exc_table + + rsr a0, EXCCAUSE + s32i a0, a2, PT_DEPC # mark it as a regular exception + addx4 a0, a0, a3 + l32i a0, a0, EXC_TABLE_FAST_USER + jx a0 + +.Lfixup:/* Check for a fixup handler or if we were in a critical section. */ + + /* a0: depc, a1: a1, a2: a2, a3: trashed, depc: a0, excsave1: a3 */ + + movi a3, exc_table + s32i a2, a3, EXC_TABLE_DOUBLE_SAVE # temporary variable + + /* Enter critical section. */ + + l32i a2, a3, EXC_TABLE_FIXUP + s32i a3, a3, EXC_TABLE_FIXUP + beq a2, a3, .Lunrecoverable_fixup # critical! + 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 a3, a3, EXC_TABLE_DOUBLE_SAVE + addi a2, a3, -PT_USER_SIZE + +.Lovfl: /* Jump to default handlers. */ + + /* a0: depc, a1: a1, a2: kstk, a3: a2, depc: a0, excsave: a3 */ + + xsr a3, DEPC + s32i a0, a2, PT_DEPC + s32i a3, a2, PT_AREG0 + + /* a0: avail, a1: a1, a2: kstk, a3: avail, depc: a2, excsave: a3 */ + + movi a3, exc_table + rsr a0, EXCCAUSE + addx4 a0, a0, a3 + 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! + */ + + .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, XCHAL_EXCCAUSE_ITLB_MISS, 1f + addi a3, a3, -XCHAL_EXCCAUSE_DTLB_MISS + bnez a3, .Lunrecoverable +1: movi a3, fast_second_level_miss_double_kernel + jx a3 + + /* 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, EXCSAVE_1 + wsr a0, EXCSAVE_1 + movi a0, unrecoverable_exception + callx0 a0 + + .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, EXCSAVE + XCHAL_DEBUGLEVEL + jx a0 + + + +/* 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. + */ + + .section .WindowVectors.text, "ax" + + +/* 4-Register Window Overflow Vector (Handler) */ + + .align 64 +.global _WindowOverflow4 +_WindowOverflow4: + s32e a0, a5, -16 + s32e a1, a5, -12 + s32e a2, a5, -8 + s32e a3, a5, -4 + rfwo + + +/* 4-Register Window Underflow Vector (Handler) */ + + .align 64 +.global _WindowUnderflow4 +_WindowUnderflow4: + l32e a0, a5, -16 + l32e a1, a5, -12 + l32e a2, a5, -8 + l32e a3, a5, -4 + rfwu + + +/* 8-Register Window Overflow Vector (Handler) */ + + .align 64 +.global _WindowOverflow8 +_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 + +/* 8-Register Window Underflow Vector (Handler) */ + + .align 64 +.global _WindowUnderflow8 +_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 + + +/* 12-Register Window Overflow Vector (Handler) */ + + .align 64 +.global _WindowOverflow12 +_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 + +/* 12-Register Window Underflow Vector (Handler) */ + + .align 64 +.global _WindowUnderflow12 +_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 + + .text + + diff --git a/arch/xtensa/kernel/vmlinux.lds.S b/arch/xtensa/kernel/vmlinux.lds.S new file mode 100644 index 0000000000..476b2b53cd --- /dev/null +++ b/arch/xtensa/kernel/vmlinux.lds.S @@ -0,0 +1,341 @@ +/* + * 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 - 2005 Tensilica Inc. + * + * Chris Zankel + * Marc Gauthier + * Joe Taylor + */ + +#include + +#include +#define _NOCLANGUAGE +#include +#include +OUTPUT_ARCH(xtensa) +ENTRY(_start) + +#if XCHAL_MEMORY_ORDER == XTHAL_BIGENDIAN +jiffies = jiffies_64 + 4; +#else +jiffies = jiffies_64; +#endif + +#define KERNELOFFSET 0x1000 + +/* 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 +{ + . = XCHAL_KSEG_CACHED_VADDR + KERNELOFFSET; + /* .text section */ + + _text = .; + _stext = .; + _ftext = .; + + .text : + { + /* The .head.text section must be the first section! */ + *(.head.text) + *(.literal .text) + *(.srom.text) + VMLINUX_SYMBOL(__sched_text_start) = .; + *(.sched.text.literal .sched.text) + VMLINUX_SYMBOL(__sched_text_end) = .; + VMLINUX_SYMBOL(__lock_text_start) = .; + *(.spinlock.text.literal .spinlock.text) + VMLINUX_SYMBOL(__lock_text_end) = .; + + } + _etext = .; + + . = ALIGN(16); + + RODATA + + /* Relocation table */ + + . = ALIGN(16); + __boot_reloc_table_start = ABSOLUTE(.); + + __relocate : { + + RELOCATE_ENTRY(_WindowVectors_text, + .WindowVectors.text); +#if 0 + RELOCATE_ENTRY(_KernelExceptionVector_literal, + .KernelExceptionVector.literal); +#endif + RELOCATE_ENTRY(_KernelExceptionVector_text, + .KernelExceptionVector.text); +#if 0 + RELOCATE_ENTRY(_UserExceptionVector_literal, + .UserExceptionVector.literal); +#endif + RELOCATE_ENTRY(_UserExceptionVector_text, + .UserExceptionVector.text); + RELOCATE_ENTRY(_DoubleExceptionVector_literal, + .DoubleExceptionVector.literal); + RELOCATE_ENTRY(_DoubleExceptionVector_text, + .DoubleExceptionVector.text); + } + __boot_reloc_table_end = ABSOLUTE(.) ; + + .fixup : { *(.fixup) } + + . = ALIGN(16); + + __ex_table : { + __start___ex_table = .; + *(__ex_table) + __stop___ex_table = .; + } + + /* Data section */ + + . = ALIGN(XCHAL_ICACHE_LINESIZE); + _fdata = .; + .data : + { + *(.data) CONSTRUCTORS + . = ALIGN(XCHAL_ICACHE_LINESIZE); + *(.data.cacheline_aligned) + } + + _edata = .; + + /* The initial task */ + . = ALIGN(8192); + .data.init_task : { *(.data.init_task) } + + /* Initialization code and data: */ + + . = ALIGN(1< + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_BLK_DEV_FD +#include +#endif +#ifdef CONFIG_NET +#include +#endif /* CONFIG_NET */ + + +/* + * String functions + */ +EXPORT_SYMBOL(memcmp); +EXPORT_SYMBOL(memset); +EXPORT_SYMBOL(memcpy); +EXPORT_SYMBOL(memmove); +EXPORT_SYMBOL(memchr); +EXPORT_SYMBOL(strcat); +EXPORT_SYMBOL(strchr); +EXPORT_SYMBOL(strlen); +EXPORT_SYMBOL(strpbrk); +EXPORT_SYMBOL(strncat); +EXPORT_SYMBOL(strnlen); +EXPORT_SYMBOL(strrchr); +EXPORT_SYMBOL(strstr); + +EXPORT_SYMBOL(enable_irq); +EXPORT_SYMBOL(disable_irq); +EXPORT_SYMBOL(kernel_thread); + +/* + * 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); + +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); + +/* + * Semaphore operations + */ +EXPORT_SYMBOL(__down); +EXPORT_SYMBOL(__down_interruptible); +EXPORT_SYMBOL(__down_trylock); +EXPORT_SYMBOL(__up); + +#ifdef CONFIG_NET +/* + * Networking support + */ +EXPORT_SYMBOL(csum_partial_copy_generic); +#endif /* CONFIG_NET */ + +/* + * Architecture-specific symbols + */ +EXPORT_SYMBOL(__xtensa_copy_user); + +/* + * Kernel hacking ... + */ + +#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE) +// FIXME EXPORT_SYMBOL(screen_info); +#endif + +EXPORT_SYMBOL(get_wchan); + +EXPORT_SYMBOL(outsb); +EXPORT_SYMBOL(outsw); +EXPORT_SYMBOL(outsl); +EXPORT_SYMBOL(insb); +EXPORT_SYMBOL(insw); +EXPORT_SYMBOL(insl);