From mboxrd@z Thu Jan 1 00:00:00 1970 From: u.kleine-koenig@pengutronix.de (=?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?=) Date: Wed, 4 Apr 2012 23:10:07 +0200 Subject: [PATCH v4 3/3] Cortex-M3: Add support for exception handling In-Reply-To: <20120404210838.GA23126@pengutronix.de> References: <20120404210838.GA23126@pengutronix.de> Message-ID: <1333573807-23709-3-git-send-email-u.kleine-koenig@pengutronix.de> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org This patch implements the exception handling for the ARMv7-M architecture (pretty different from the A or R profiles). It bases on work done earlier by Catalin for 2.6.33 but was nearly completely rewritten to use a pt_regs layout compatible to the A profile. Signed-off-by: Catalin Marinas Signed-off-by: Uwe Kleine-K?nig --- Hello, apart from rebasing on top of 3.4-rc1 this patch is unchanged compared to v3. Best regards Uwe arch/arm/kernel/entry-common.S | 4 ++ arch/arm/kernel/entry-header.S | 148 ++++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/entry-v7m.S | 134 ++++++++++++++++++++++++++++++++++++ arch/arm/kernel/process.c | 8 +++ arch/arm/kernel/ptrace.c | 3 + arch/arm/kernel/signal.c | 13 ++++ arch/arm/kernel/sys_arm.c | 1 + 7 files changed, 311 insertions(+) create mode 100644 arch/arm/kernel/entry-v7m.S diff --git a/arch/arm/kernel/entry-common.S b/arch/arm/kernel/entry-common.S index 54ee265..3e7c216 100644 --- a/arch/arm/kernel/entry-common.S +++ b/arch/arm/kernel/entry-common.S @@ -351,6 +351,9 @@ ENDPROC(ftrace_stub) .align 5 ENTRY(vector_swi) +#ifdef CONFIG_CPU_V7M + v7m_exception_entry +#else sub sp, sp, #S_FRAME_SIZE stmia sp, {r0 - r12} @ Calling r0 - r12 ARM( add r8, sp, #S_PC ) @@ -361,6 +364,7 @@ ENTRY(vector_swi) str lr, [sp, #S_PC] @ Save calling PC str r8, [sp, #S_PSR] @ Save CPSR str r0, [sp, #S_OLD_R0] @ Save OLD_R0 +#endif zero_fp /* diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 9a8531e..33d9900 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -44,6 +44,145 @@ #endif .endm +#ifdef CONFIG_CPU_V7M +/* + * ARMv7-M exception entry/exit macros. + * + * xPSR, ReturnAddress(), LR (R14), R12, R3, R2, R1, and R0 are + * automatically saved on the current stack (32 words) before + * switching to the exception stack (SP_main). + * + * If exception is taken while in user mode, SP_main is + * empty. Otherwise, SP_main is aligned to 64 bit automatically + * (CCR.STKALIGN set). + * + * Linux assumes that the interrupts are disabled when entering an + * exception handler and it may BUG if this is not the case. Interrupts + * are disabled during entry and reenabled in the exit macro. + * + * v7m_exception_fast_exit is used when returning from interrupts. + * + * v7m_exception_slow_exit is used when returning from SVC or PendSV. + * When returning to kernel mode, we don't return from exception. + */ + .macro v7m_exception_entry + @ determine the location of the registers saved by the core during + @ exception entry. Depending on the mode the cpu was in when the + @ exception happend that is either on the main or the process stack. + @ Bit 2 of EXC_RETURN stored in the lr register specifies which stack + @ was used. + tst lr, #0x4 + mrsne r12, psp + moveq r12, sp + + @ we cannot rely on r0-r3 and r12 matching the value saved in the + @ exception frame because of tail-chaining. So these have to be + @ reloaded. + ldmia r12!, {r0-r3} + + @ Linux expects to have irqs off. Do it here before taking stack space + cpsid i + + sub sp, #S_FRAME_SIZE-S_IP + stmdb sp!, {r0-r11} + + @ load saved r12, lr, return address and xPSR. + @ r0-r7 are used for signals and never touched from now on. Clobbering + @ r8-r12 is OK. + mov r9, r12 + ldmia r9!, {r8, r10-r12} + + @ calculate the original stack pointer value. + @ r9 currently points to the memory location just above the auto saved + @ xPSR. If the FP extension is implemented and bit 4 of EXC_RETURN is 0 + @ then space was allocated for FP state. That is space for 18 32-bit + @ values. (If FP extension is unimplemented, bit 4 is 1.) + @ Additionally the cpu might automatically 8-byte align the stack. Bit 9 + @ of the saved xPSR specifies if stack aligning took place. In this case + @ another 32-bit value is included in the stack. + + tst lr, #0x10 + addeq r9, r9, #576 + + tst r12, 0x100 + addne r9, r9, #4 + + @ store saved r12 using str to have a register to hold the base for stm + str r8, [sp, #S_IP] + add r8, sp, #S_SP + @ store r13-r15, xPSR + stmia r8!, {r9-r12} + @ store r0 once more and EXC_RETURN + stmia r8, {r0, lr} + .endm + + .macro v7m_exception_fast_exit + @ registers r0-r3 and r12 are automatically restored on exception + @ return. r4-r7 were not clobbered in v7m_exception_entry so for + @ correctness they don't need to be restored. So only r8-r11 must be + @ restored here. The easiest way to do so is to restore r0-r7, too. + ldmia sp!, {r0-r11} + add sp, #S_FRAME_SIZE-S_IP + cpsie i + bx lr + .endm + + .macro v7m_exception_slow_exit ret_r0 + cpsid i + ldr lr, [sp, #S_EXC_RET] @ read exception LR + tst lr, #0x8 + bne 1f @ go to thread mode using exception return + + /* + * return to kernel thread + * sp is already set up (and might be unset in pt_regs), so only + * restore r0-r12 and pc + */ + ldmia sp, {r0-r12} + ldr lr, [sp, #S_PC] + add sp, sp, #S_FRAME_SIZE + cpsie i + bx lr + +1: /* + * return to userspace + */ + + @ read original r12, sp, lr, pc and xPSR + add r12, sp, #S_IP + ldmia r12, {r1-r5} + + @ handle stack aligning + tst r5, #0x100 + subne r2, r2, #4 + + @ skip over stack space for fp saving + tst lr, #0x10 + subeq r2, r2, #576 + + @ write basic exception frame + stmdb r2!, {r1, r3-r5} + ldmia sp, {r1, r3-r5} + .if \ret_r0 + stmdb r2!, {r0, r3-r5} + .else + stmdb r2!, {r1, r3-r5} + .endif + + @ restore process sp + msr psp, r2 + + @ restore original r4-r11 + ldmia sp!, {r0-r11} + + @ restore main sp + add sp, sp, #S_FRAME_SIZE-S_IP + + cpsie i + bx lr + .endm +#endif /* CONFIG_CPU_V7M */ + @ @ Store/load the USER SP and LR registers by switching to the SYS @ mode. Useful in Thumb-2 mode where "stm/ldm rd, {sp, lr}^" is not @@ -131,6 +270,14 @@ rfeia sp! .endm +#ifdef CONFIG_CPU_V7M + .macro restore_user_regs, fast = 0, offset = 0 + .if \offset + add sp, #\offset + .endif + v7m_exception_slow_exit ret_r0 = \fast + .endm +#else /* !CONFIG_CPU_V7M */ .macro restore_user_regs, fast = 0, offset = 0 clrex @ clear the exclusive monitor mov r2, sp @@ -147,6 +294,7 @@ add sp, sp, #S_FRAME_SIZE - S_SP movs pc, lr @ return & move spsr_svc into cpsr .endm +#endif /* CONFIG_CPU_V7M */ .macro get_thread_info, rd mov \rd, sp diff --git a/arch/arm/kernel/entry-v7m.S b/arch/arm/kernel/entry-v7m.S new file mode 100644 index 0000000..a0991dc --- /dev/null +++ b/arch/arm/kernel/entry-v7m.S @@ -0,0 +1,134 @@ +/* + * linux/arch/arm/kernel/entry-v7m.S + * + * Copyright (C) 2008 ARM Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Low-level vector interface routines for the ARMv7-M architecture + */ +#include +#include +#include + +#include + +#include "entry-header.S" + +#ifdef CONFIG_PREEMPT +#error "CONFIG_PREEMPT not supported on the current ARMv7M implementation" +#endif +#ifdef CONFIG_TRACE_IRQFLAGS +#error "CONFIG_TRACE_IRQFLAGS not supported on the current ARMv7M implementation" +#endif + +__invalid_entry: + v7m_exception_entry + adr r0, strerr + mrs r1, ipsr + mov r2, lr + bl printk + mov r0, sp + bl show_regs +1: b 1b +ENDPROC(__invalid_entry) + +strerr: .asciz "\nUnhandled exception: IPSR = %08lx LR = %08lx\n" + + .align 2 +__irq_entry: + v7m_exception_entry + + @ + @ Invoke the IRQ handler + @ + mrs r0, ipsr + and r0, #0xff + sub r0, #16 @ IRQ number + mov r1, sp + @ routine called with r0 = irq number, r1 = struct pt_regs * + bl asm_do_IRQ + + @ + @ Check for any pending work if returning to user + @ + ldr lr, [sp, #S_EXC_RET] + tst lr, #0x8 @ check the return stack + beq 2f @ returning to handler mode + get_thread_info tsk + ldr r1, [tsk, #TI_FLAGS] + tst r1, #_TIF_WORK_MASK + beq 2f @ no work pending + ldr r1, =0xe000ed04 @ ICSR + mov r0, #1 << 28 @ ICSR.PENDSVSET + str r0, [r1] @ raise PendSV + +2: + v7m_exception_fast_exit +ENDPROC(__irq_entry) + +__pendsv_entry: + v7m_exception_entry + + ldr r1, =0xe000ed04 @ ICSR + mov r0, #1 << 27 @ ICSR.PENDSVCLR + str r0, [r1] @ clear PendSV + + @ execute the pending work, including reschedule + get_thread_info tsk + mov why, #0 + b ret_to_user +ENDPROC(__pendsv_entry) + +/* + * Register switch for ARMv7-M processors. + * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info + * previous and next are guaranteed not to be the same. + */ +ENTRY(__switch_to) + .fnstart + .cantunwind + add ip, r1, #TI_CPU_SAVE + stmia ip!, {r4 - r11} @ Store most regs on stack + str sp, [ip], #4 + str lr, [ip], #4 + mov r5, r0 + add r4, r2, #TI_CPU_SAVE + ldr r0, =thread_notify_head + mov r1, #THREAD_NOTIFY_SWITCH + bl atomic_notifier_call_chain + mov ip, r4 + mov r0, r5 + ldmia ip!, {r4 - r11} @ Load all regs saved previously + ldr sp, [ip], #4 + ldr pc, [ip] + .fnend +ENDPROC(__switch_to) + + .data + .align 8 +/* + * Vector table (64 words => 256 bytes natural alignment) + */ +ENTRY(vector_table) + .long 0 @ 0 - Reset stack pointer + .long __invalid_entry @ 1 - Reset + .long __invalid_entry @ 2 - NMI + .long __invalid_entry @ 3 - HardFault + .long __invalid_entry @ 4 - MemManage + .long __invalid_entry @ 5 - BusFault + .long __invalid_entry @ 6 - UsageFault + .long __invalid_entry @ 7 - Reserved + .long __invalid_entry @ 8 - Reserved + .long __invalid_entry @ 9 - Reserved + .long __invalid_entry @ 10 - Reserved + .long vector_swi @ 11 - SVCall + .long __invalid_entry @ 12 - Debug Monitor + .long __invalid_entry @ 13 - Reserved + .long __pendsv_entry @ 14 - PendSV + .long __invalid_entry @ 15 - SysTick + .rept 64 - 16 + .long __irq_entry @ 16..64 - External Interrupts + .endr diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index 2b7b017..9f301dc 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c @@ -454,7 +454,11 @@ asm( ".pushsection .text\n" #ifdef CONFIG_TRACE_IRQFLAGS " bl trace_hardirqs_on\n" #endif +#ifdef CONFIG_CPU_V7M +" msr primask, r7\n" +#else " msr cpsr_c, r7\n" +#endif " mov r0, r4\n" " mov lr, r6\n" " mov pc, r5\n" @@ -493,6 +497,10 @@ pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) regs.ARM_r7 = SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE; regs.ARM_pc = (unsigned long)kernel_thread_helper; regs.ARM_cpsr = regs.ARM_r7 | PSR_I_BIT; +#if defined CONFIG_CPU_V7M + /* Return to Handler mode */ + regs.ARM_EXC_RET = 0xfffffff1L; +#endif return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); } diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c index 45956c9..49383a4 100644 --- a/arch/arm/kernel/ptrace.c +++ b/arch/arm/kernel/ptrace.c @@ -82,6 +82,9 @@ static const struct pt_regs_offset regoffset_table[] = { REG_OFFSET_NAME(pc), REG_OFFSET_NAME(cpsr), REG_OFFSET_NAME(ORIG_r0), +#ifdef CONFIG_CPU_V7M + REG_OFFSET_NAME(EXC_RET), +#endif REG_OFFSET_END, }; diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c index 7cb532f..be61a41 100644 --- a/arch/arm/kernel/signal.c +++ b/arch/arm/kernel/signal.c @@ -278,6 +278,8 @@ static int restore_sigframe(struct pt_regs *regs, struct sigframe __user *sf) sigset_t set; int err; + pr_info("%s(regs=%p)\n", __func__, regs); + err = __copy_from_user(&set, &sf->uc.uc_sigmask, sizeof(set)); if (err == 0) { sigdelsetmask(&set, ~_BLOCKABLE); @@ -325,6 +327,7 @@ asmlinkage int sys_sigreturn(struct pt_regs *regs) { struct sigframe __user *frame; + pr_info("%s(regs=%p)\n", __func__, regs); /* Always make any pending restarted system calls return -EINTR */ current_thread_info()->restart_block.fn = do_no_restart_syscall; @@ -355,6 +358,7 @@ asmlinkage int sys_rt_sigreturn(struct pt_regs *regs) { struct rt_sigframe __user *frame; + pr_info("%s(regs=%p)\n", __func__, regs); /* Always make any pending restarted system calls return -EINTR */ current_thread_info()->restart_block.fn = do_no_restart_syscall; @@ -439,6 +443,7 @@ get_sigframe(struct k_sigaction *ka, struct pt_regs *regs, int framesize) unsigned long sp = regs->ARM_sp; void __user *frame; + pr_info("%s(regs=%p)\n", __func__, regs); /* * This is the X/Open sanctioned signal stack switching. */ @@ -468,6 +473,8 @@ setup_return(struct pt_regs *regs, struct k_sigaction *ka, int thumb = 0; unsigned long cpsr = regs->ARM_cpsr & ~(PSR_f | PSR_E_BIT); + pr_info("%s(regs=%p)\n", __func__, regs); + cpsr |= PSR_ENDSTATE; /* @@ -540,6 +547,8 @@ setup_frame(int usig, struct k_sigaction *ka, sigset_t *set, struct pt_regs *reg struct sigframe __user *frame = get_sigframe(ka, regs, sizeof(*frame)); int err = 0; + pr_info("%s(regs=%p)\n", __func__, regs); + if (!frame) return 1; @@ -563,6 +572,8 @@ setup_rt_frame(int usig, struct k_sigaction *ka, siginfo_t *info, stack_t stack; int err = 0; + pr_info("%s(regs=%p)\n", __func__, regs); + if (!frame) return 1; @@ -607,6 +618,7 @@ handle_signal(unsigned long sig, struct k_sigaction *ka, int usig = sig; int ret; + pr_info("%s(regs=%p)\n", __func__, regs); /* * translate the signal */ @@ -776,6 +788,7 @@ static void do_signal(struct pt_regs *regs, int syscall) asmlinkage void do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall) { + pr_info("%s(regs=%p)\n", __func__, regs); if (thread_flags & _TIF_SIGPENDING) do_signal(regs, syscall); diff --git a/arch/arm/kernel/sys_arm.c b/arch/arm/kernel/sys_arm.c index d2b1779..4d3caca4d 100644 --- a/arch/arm/kernel/sys_arm.c +++ b/arch/arm/kernel/sys_arm.c @@ -102,6 +102,7 @@ int kernel_execve(const char *filename, * We were successful. We won't be returning to our caller, but * instead to user space by manipulating the kernel stack. */ + pr_info("we were successful\n"); asm( "add r0, %0, %1\n\t" "mov r1, %2\n\t" "mov r2, %3\n\t" -- 1.7.9.5