![Adrian Bunk](/assets/img/avatar_default.png)
CONFIG_ALIGNMENT_TRAP is never set on arm26. Signed-off-by: Adrian Bunk <bunk@stusta.de> Acked-by: Ian Molton <spyro@f2s.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
952 lines
24 KiB
ArmAsm
952 lines
24 KiB
ArmAsm
/* arch/arm26/kernel/entry.S
|
|
*
|
|
* Assembled from chunks of code in arch/arm
|
|
*
|
|
* Copyright (C) 2003 Ian Molton
|
|
* Based on the work of RMK.
|
|
*
|
|
*/
|
|
|
|
#include <linux/linkage.h>
|
|
|
|
#include <asm/assembler.h>
|
|
#include <asm/asm-offsets.h>
|
|
#include <asm/errno.h>
|
|
#include <asm/hardware.h>
|
|
#include <asm/sysirq.h>
|
|
#include <asm/thread_info.h>
|
|
#include <asm/page.h>
|
|
#include <asm/ptrace.h>
|
|
|
|
.macro zero_fp
|
|
#ifndef CONFIG_NO_FRAME_POINTER
|
|
mov fp, #0
|
|
#endif
|
|
.endm
|
|
|
|
.text
|
|
|
|
@ Bad Abort numbers
|
|
@ -----------------
|
|
@
|
|
#define BAD_PREFETCH 0
|
|
#define BAD_DATA 1
|
|
#define BAD_ADDREXCPTN 2
|
|
#define BAD_IRQ 3
|
|
#define BAD_UNDEFINSTR 4
|
|
|
|
@ OS version number used in SWIs
|
|
@ RISC OS is 0
|
|
@ RISC iX is 8
|
|
@
|
|
#define OS_NUMBER 9
|
|
#define ARMSWI_OFFSET 0x000f0000
|
|
|
|
@
|
|
@ Stack format (ensured by USER_* and SVC_*)
|
|
@ PSR and PC are comined on arm26
|
|
@
|
|
|
|
#define S_OFF 8
|
|
|
|
#define S_OLD_R0 64
|
|
#define S_PC 60
|
|
#define S_LR 56
|
|
#define S_SP 52
|
|
#define S_IP 48
|
|
#define S_FP 44
|
|
#define S_R10 40
|
|
#define S_R9 36
|
|
#define S_R8 32
|
|
#define S_R7 28
|
|
#define S_R6 24
|
|
#define S_R5 20
|
|
#define S_R4 16
|
|
#define S_R3 12
|
|
#define S_R2 8
|
|
#define S_R1 4
|
|
#define S_R0 0
|
|
|
|
.macro save_user_regs
|
|
str r0, [sp, #-4]! @ Store SVC r0
|
|
str lr, [sp, #-4]! @ Store user mode PC
|
|
sub sp, sp, #15*4
|
|
stmia sp, {r0 - lr}^ @ Store the other user-mode regs
|
|
mov r0, r0
|
|
.endm
|
|
|
|
.macro slow_restore_user_regs
|
|
ldmia sp, {r0 - lr}^ @ restore the user regs not including PC
|
|
mov r0, r0
|
|
ldr lr, [sp, #15*4] @ get user PC
|
|
add sp, sp, #15*4+8 @ free stack
|
|
movs pc, lr @ return
|
|
.endm
|
|
|
|
.macro fast_restore_user_regs
|
|
add sp, sp, #S_OFF
|
|
ldmib sp, {r1 - lr}^
|
|
mov r0, r0
|
|
ldr lr, [sp, #15*4]
|
|
add sp, sp, #15*4+8
|
|
movs pc, lr
|
|
.endm
|
|
|
|
.macro save_svc_regs
|
|
str sp, [sp, #-16]!
|
|
str lr, [sp, #8]
|
|
str lr, [sp, #4]
|
|
stmfd sp!, {r0 - r12}
|
|
mov r0, #-1
|
|
str r0, [sp, #S_OLD_R0]
|
|
zero_fp
|
|
.endm
|
|
|
|
.macro save_svc_regs_irq
|
|
str sp, [sp, #-16]!
|
|
str lr, [sp, #4]
|
|
ldr lr, .LCirq
|
|
ldr lr, [lr]
|
|
str lr, [sp, #8]
|
|
stmfd sp!, {r0 - r12}
|
|
mov r0, #-1
|
|
str r0, [sp, #S_OLD_R0]
|
|
zero_fp
|
|
.endm
|
|
|
|
.macro restore_svc_regs
|
|
ldmfd sp, {r0 - pc}^
|
|
.endm
|
|
|
|
.macro mask_pc, rd, rm
|
|
bic \rd, \rm, #PCMASK
|
|
.endm
|
|
|
|
.macro disable_irqs, temp
|
|
mov \temp, pc
|
|
orr \temp, \temp, #PSR_I_BIT
|
|
teqp \temp, #0
|
|
.endm
|
|
|
|
.macro enable_irqs, temp
|
|
mov \temp, pc
|
|
and \temp, \temp, #~PSR_I_BIT
|
|
teqp \temp, #0
|
|
.endm
|
|
|
|
.macro initialise_traps_extra
|
|
.endm
|
|
|
|
.macro get_thread_info, rd
|
|
mov \rd, sp, lsr #13
|
|
mov \rd, \rd, lsl #13
|
|
.endm
|
|
|
|
/*
|
|
* These are the registers used in the syscall handler, and allow us to
|
|
* have in theory up to 7 arguments to a function - r0 to r6.
|
|
*
|
|
* Note that tbl == why is intentional.
|
|
*
|
|
* We must set at least "tsk" and "why" when calling ret_with_reschedule.
|
|
*/
|
|
scno .req r7 @ syscall number
|
|
tbl .req r8 @ syscall table pointer
|
|
why .req r8 @ Linux syscall (!= 0)
|
|
tsk .req r9 @ current thread_info
|
|
|
|
/*
|
|
* Get the system call number.
|
|
*/
|
|
.macro get_scno
|
|
mask_pc lr, lr
|
|
ldr scno, [lr, #-4] @ get SWI instruction
|
|
.endm
|
|
/*
|
|
* -----------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* We rely on the fact that R0 is at the bottom of the stack (due to
|
|
* slow/fast restore user regs).
|
|
*/
|
|
#if S_R0 != 0
|
|
#error "Please fix"
|
|
#endif
|
|
|
|
/*
|
|
* This is the fast syscall return path. We do as little as
|
|
* possible here, and this includes saving r0 back into the SVC
|
|
* stack.
|
|
*/
|
|
ret_fast_syscall:
|
|
disable_irqs r1 @ disable interrupts
|
|
ldr r1, [tsk, #TI_FLAGS]
|
|
tst r1, #_TIF_WORK_MASK
|
|
bne fast_work_pending
|
|
fast_restore_user_regs
|
|
|
|
/*
|
|
* Ok, we need to do extra processing, enter the slow path.
|
|
*/
|
|
fast_work_pending:
|
|
str r0, [sp, #S_R0+S_OFF]! @ returned r0
|
|
work_pending:
|
|
tst r1, #_TIF_NEED_RESCHED
|
|
bne work_resched
|
|
tst r1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING
|
|
beq no_work_pending
|
|
mov r0, sp @ 'regs'
|
|
mov r2, why @ 'syscall'
|
|
bl do_notify_resume
|
|
disable_irqs r1 @ disable interrupts
|
|
b no_work_pending
|
|
|
|
work_resched:
|
|
bl schedule
|
|
/*
|
|
* "slow" syscall return path. "why" tells us if this was a real syscall.
|
|
*/
|
|
ENTRY(ret_to_user)
|
|
ret_slow_syscall:
|
|
disable_irqs r1 @ disable interrupts
|
|
ldr r1, [tsk, #TI_FLAGS]
|
|
tst r1, #_TIF_WORK_MASK
|
|
bne work_pending
|
|
no_work_pending:
|
|
slow_restore_user_regs
|
|
|
|
/*
|
|
* This is how we return from a fork.
|
|
*/
|
|
ENTRY(ret_from_fork)
|
|
bl schedule_tail
|
|
get_thread_info tsk
|
|
ldr r1, [tsk, #TI_FLAGS] @ check for syscall tracing
|
|
mov why, #1
|
|
tst r1, #_TIF_SYSCALL_TRACE @ are we tracing syscalls?
|
|
beq ret_slow_syscall
|
|
mov r1, sp
|
|
mov r0, #1 @ trace exit [IP = 1]
|
|
bl syscall_trace
|
|
b ret_slow_syscall
|
|
|
|
// FIXME - is this strictly necessary?
|
|
#include "calls.S"
|
|
|
|
/*=============================================================================
|
|
* SWI handler
|
|
*-----------------------------------------------------------------------------
|
|
*/
|
|
|
|
.align 5
|
|
ENTRY(vector_swi)
|
|
save_user_regs
|
|
zero_fp
|
|
get_scno
|
|
|
|
enable_irqs ip
|
|
|
|
str r4, [sp, #-S_OFF]! @ push fifth arg
|
|
|
|
get_thread_info tsk
|
|
ldr ip, [tsk, #TI_FLAGS] @ check for syscall tracing
|
|
bic scno, scno, #0xff000000 @ mask off SWI op-code
|
|
eor scno, scno, #OS_NUMBER << 20 @ check OS number
|
|
adr tbl, sys_call_table @ load syscall table pointer
|
|
tst ip, #_TIF_SYSCALL_TRACE @ are we tracing syscalls?
|
|
bne __sys_trace
|
|
|
|
adral lr, ret_fast_syscall @ set return address
|
|
orral lr, lr, #PSR_I_BIT | MODE_SVC26 @ Force SVC mode on return
|
|
cmp scno, #NR_syscalls @ check upper syscall limit
|
|
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
|
|
|
|
add r1, sp, #S_OFF
|
|
2: mov why, #0 @ no longer a real syscall
|
|
cmp scno, #ARMSWI_OFFSET
|
|
eor r0, scno, #OS_NUMBER << 20 @ put OS number back
|
|
bcs arm_syscall
|
|
b sys_ni_syscall @ not private func
|
|
|
|
/*
|
|
* This is the really slow path. We're going to be doing
|
|
* context switches, and waiting for our parent to respond.
|
|
*/
|
|
__sys_trace:
|
|
add r1, sp, #S_OFF
|
|
mov r0, #0 @ trace entry [IP = 0]
|
|
bl syscall_trace
|
|
|
|
adral lr, __sys_trace_return @ set return address
|
|
orral lr, lr, #PSR_I_BIT | MODE_SVC26 @ Force SVC mode on return
|
|
add r1, sp, #S_R0 + S_OFF @ pointer to regs
|
|
cmp scno, #NR_syscalls @ check upper syscall limit
|
|
ldmccia r1, {r0 - r3} @ have to reload r0 - r3
|
|
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
|
|
b 2b
|
|
|
|
__sys_trace_return:
|
|
str r0, [sp, #S_R0 + S_OFF]! @ save returned r0
|
|
mov r1, sp
|
|
mov r0, #1 @ trace exit [IP = 1]
|
|
bl syscall_trace
|
|
b ret_slow_syscall
|
|
|
|
.align 5
|
|
|
|
.type sys_call_table, #object
|
|
ENTRY(sys_call_table)
|
|
#include "calls.S"
|
|
|
|
/*============================================================================
|
|
* Special system call wrappers
|
|
*/
|
|
@ r0 = syscall number
|
|
@ r5 = syscall table
|
|
.type sys_syscall, #function
|
|
sys_syscall:
|
|
eor scno, r0, #OS_NUMBER << 20
|
|
cmp scno, #NR_syscalls @ check range
|
|
stmleia sp, {r5, r6} @ shuffle args
|
|
movle r0, r1
|
|
movle r1, r2
|
|
movle r2, r3
|
|
movle r3, r4
|
|
ldrle pc, [tbl, scno, lsl #2]
|
|
b sys_ni_syscall
|
|
|
|
sys_fork_wrapper:
|
|
add r0, sp, #S_OFF
|
|
b sys_fork
|
|
|
|
sys_vfork_wrapper:
|
|
add r0, sp, #S_OFF
|
|
b sys_vfork
|
|
|
|
sys_execve_wrapper:
|
|
add r3, sp, #S_OFF
|
|
b sys_execve
|
|
|
|
sys_clone_wapper:
|
|
add r2, sp, #S_OFF
|
|
b sys_clone
|
|
|
|
sys_sigsuspend_wrapper:
|
|
add r3, sp, #S_OFF
|
|
b sys_sigsuspend
|
|
|
|
sys_rt_sigsuspend_wrapper:
|
|
add r2, sp, #S_OFF
|
|
b sys_rt_sigsuspend
|
|
|
|
sys_sigreturn_wrapper:
|
|
add r0, sp, #S_OFF
|
|
b sys_sigreturn
|
|
|
|
sys_rt_sigreturn_wrapper:
|
|
add r0, sp, #S_OFF
|
|
b sys_rt_sigreturn
|
|
|
|
sys_sigaltstack_wrapper:
|
|
ldr r2, [sp, #S_OFF + S_SP]
|
|
b do_sigaltstack
|
|
|
|
/*
|
|
* Note: off_4k (r5) is always units of 4K. If we can't do the requested
|
|
* offset, we return EINVAL. FIXME - this lost some stuff from arm32 to
|
|
* ifdefs. check it out.
|
|
*/
|
|
sys_mmap2:
|
|
tst r5, #((1 << (PAGE_SHIFT - 12)) - 1)
|
|
moveq r5, r5, lsr #PAGE_SHIFT - 12
|
|
streq r5, [sp, #4]
|
|
beq do_mmap2
|
|
mov r0, #-EINVAL
|
|
RETINSTR(mov,pc, lr)
|
|
|
|
/*
|
|
* Design issues:
|
|
* - We have several modes that each vector can be called from,
|
|
* each with its own set of registers. On entry to any vector,
|
|
* we *must* save the registers used in *that* mode.
|
|
*
|
|
* - This code must be as fast as possible.
|
|
*
|
|
* There are a few restrictions on the vectors:
|
|
* - the SWI vector cannot be called from *any* non-user mode
|
|
*
|
|
* - the FP emulator is *never* called from *any* non-user mode undefined
|
|
* instruction.
|
|
*
|
|
*/
|
|
|
|
.text
|
|
|
|
.macro handle_irq
|
|
1: mov r4, #IOC_BASE
|
|
ldrb r6, [r4, #0x24] @ get high priority first
|
|
adr r5, irq_prio_h
|
|
teq r6, #0
|
|
ldreqb r6, [r4, #0x14] @ get low priority
|
|
adreq r5, irq_prio_l
|
|
|
|
teq r6, #0 @ If an IRQ happened...
|
|
ldrneb r0, [r5, r6] @ get IRQ number
|
|
movne r1, sp @ get struct pt_regs
|
|
adrne lr, 1b @ Set return address to 1b
|
|
orrne lr, lr, #PSR_I_BIT | MODE_SVC26 @ (and force SVC mode)
|
|
bne asm_do_IRQ @ process IRQ (if asserted)
|
|
.endm
|
|
|
|
|
|
/*
|
|
* Interrupt table (incorporates priority)
|
|
*/
|
|
.macro irq_prio_table
|
|
irq_prio_l: .byte 0, 0, 1, 0, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3
|
|
.byte 4, 0, 1, 0, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3
|
|
.byte 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
|
|
.byte 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
|
|
.byte 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 3, 3
|
|
.byte 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 3, 3
|
|
.byte 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
|
|
.byte 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
|
|
.byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
|
.byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
|
.byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
|
.byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
|
.byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
|
.byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
|
.byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
|
.byte 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
|
|
irq_prio_h: .byte 0, 8, 9, 8,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 12, 8, 9, 8,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 14,14,14,14,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 14,14,14,14,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 15,15,15,15,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 15,15,15,15,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 15,15,15,15,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 15,15,15,15,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.byte 13,13,13,13,10,10,10,10,11,11,11,11,10,10,10,10
|
|
.endm
|
|
|
|
#if 1
|
|
/*
|
|
* Uncomment these if you wish to get more debugging into about data aborts.
|
|
* FIXME - I bet we can find a way to encode these and keep performance.
|
|
*/
|
|
#define FAULT_CODE_LDRSTRPOST 0x80
|
|
#define FAULT_CODE_LDRSTRPRE 0x40
|
|
#define FAULT_CODE_LDRSTRREG 0x20
|
|
#define FAULT_CODE_LDMSTM 0x10
|
|
#define FAULT_CODE_LDCSTC 0x08
|
|
#endif
|
|
#define FAULT_CODE_PREFETCH 0x04
|
|
#define FAULT_CODE_WRITE 0x02
|
|
#define FAULT_CODE_FORCECOW 0x01
|
|
|
|
/*=============================================================================
|
|
* Undefined FIQs
|
|
*-----------------------------------------------------------------------------
|
|
*/
|
|
_unexp_fiq: ldr sp, .LCfiq
|
|
mov r12, #IOC_BASE
|
|
strb r12, [r12, #0x38] @ Disable FIQ register
|
|
teqp pc, #PSR_I_BIT | PSR_F_BIT | MODE_SVC26
|
|
mov r0, r0
|
|
stmfd sp!, {r0 - r3, ip, lr}
|
|
adr r0, Lfiqmsg
|
|
bl printk
|
|
ldmfd sp!, {r0 - r3, ip, lr}
|
|
teqp pc, #PSR_I_BIT | PSR_F_BIT | MODE_FIQ26
|
|
mov r0, r0
|
|
movs pc, lr
|
|
|
|
Lfiqmsg: .ascii "*** Unexpected FIQ\n\0"
|
|
.align
|
|
|
|
.LCfiq: .word __temp_fiq
|
|
.LCirq: .word __temp_irq
|
|
|
|
/*=============================================================================
|
|
* Undefined instruction handler
|
|
*-----------------------------------------------------------------------------
|
|
* Handles floating point instructions
|
|
*/
|
|
vector_undefinstr:
|
|
tst lr, #MODE_SVC26 @ did we come from a non-user mode?
|
|
bne __und_svc @ yes - deal with it.
|
|
/* Otherwise, fall through for the user-space (common) case. */
|
|
save_user_regs
|
|
zero_fp @ zero frame pointer
|
|
teqp pc, #PSR_I_BIT | MODE_SVC26 @ disable IRQs
|
|
.Lbug_undef:
|
|
ldr r4, .LC2
|
|
ldr pc, [r4] @ Call FP module entry point
|
|
/* FIXME - should we trap for a null pointer here? */
|
|
|
|
/* The SVC mode case */
|
|
__und_svc: save_svc_regs @ Non-user mode
|
|
mask_pc r0, lr
|
|
and r2, lr, #3
|
|
sub r0, r0, #4
|
|
mov r1, sp
|
|
bl do_undefinstr
|
|
restore_svc_regs
|
|
|
|
/* We get here if the FP emulator doesnt handle the undef instr.
|
|
* If the insn WAS handled, the emulator jumps to ret_from_exception by itself/
|
|
*/
|
|
.globl fpundefinstr
|
|
fpundefinstr:
|
|
mov r0, lr
|
|
mov r1, sp
|
|
teqp pc, #MODE_SVC26
|
|
bl do_undefinstr
|
|
b ret_from_exception @ Normal FP exit
|
|
|
|
#if defined CONFIG_FPE_NWFPE || defined CONFIG_FPE_FASTFPE
|
|
/* The FPE is always present */
|
|
.equ fpe_not_present, 0
|
|
#else
|
|
/* We get here if an undefined instruction happens and the floating
|
|
* point emulator is not present. If the offending instruction was
|
|
* a WFS, we just perform a normal return as if we had emulated the
|
|
* operation. This is a hack to allow some basic userland binaries
|
|
* to run so that the emulator module proper can be loaded. --philb
|
|
* FIXME - probably a broken useless hack...
|
|
*/
|
|
fpe_not_present:
|
|
adr r10, wfs_mask_data
|
|
ldmia r10, {r4, r5, r6, r7, r8}
|
|
ldr r10, [sp, #S_PC] @ Load PC
|
|
sub r10, r10, #4
|
|
mask_pc r10, r10
|
|
ldrt r10, [r10] @ get instruction
|
|
and r5, r10, r5
|
|
teq r5, r4 @ Is it WFS?
|
|
beq ret_from_exception
|
|
and r5, r10, r8
|
|
teq r5, r6 @ Is it LDF/STF on sp or fp?
|
|
teqne r5, r7
|
|
bne fpundefinstr
|
|
tst r10, #0x00200000 @ Does it have WB
|
|
beq ret_from_exception
|
|
and r4, r10, #255 @ get offset
|
|
and r6, r10, #0x000f0000
|
|
tst r10, #0x00800000 @ +/-
|
|
ldr r5, [sp, r6, lsr #14] @ Load reg
|
|
rsbeq r4, r4, #0
|
|
add r5, r5, r4, lsl #2
|
|
str r5, [sp, r6, lsr #14] @ Save reg
|
|
b ret_from_exception
|
|
|
|
wfs_mask_data: .word 0x0e200110 @ WFS/RFS
|
|
.word 0x0fef0fff
|
|
.word 0x0d0d0100 @ LDF [sp]/STF [sp]
|
|
.word 0x0d0b0100 @ LDF [fp]/STF [fp]
|
|
.word 0x0f0f0f00
|
|
#endif
|
|
|
|
.LC2: .word fp_enter
|
|
|
|
/*=============================================================================
|
|
* Prefetch abort handler
|
|
*-----------------------------------------------------------------------------
|
|
*/
|
|
#define DEBUG_UNDEF
|
|
/* remember: lr = USR pc */
|
|
vector_prefetch:
|
|
sub lr, lr, #4
|
|
tst lr, #MODE_SVC26
|
|
bne __pabt_invalid
|
|
save_user_regs
|
|
teqp pc, #MODE_SVC26 @ Enable IRQs...
|
|
mask_pc r0, lr @ Address of abort
|
|
mov r1, sp @ Tasks registers
|
|
bl do_PrefetchAbort
|
|
teq r0, #0 @ If non-zero, we believe this abort..
|
|
bne ret_from_exception
|
|
#ifdef DEBUG_UNDEF
|
|
adr r0, t
|
|
bl printk
|
|
#endif
|
|
ldr lr, [sp,#S_PC] @ FIXME program to test this on. I think its
|
|
b .Lbug_undef @ broken at the moment though!)
|
|
|
|
__pabt_invalid: save_svc_regs
|
|
mov r0, sp @ Prefetch aborts are definitely *not*
|
|
mov r1, #BAD_PREFETCH @ allowed in non-user modes. We cant
|
|
and r2, lr, #3 @ recover from this problem.
|
|
b bad_mode
|
|
|
|
#ifdef DEBUG_UNDEF
|
|
t: .ascii "*** undef ***\r\n\0"
|
|
.align
|
|
#endif
|
|
|
|
/*=============================================================================
|
|
* Address exception handler
|
|
*-----------------------------------------------------------------------------
|
|
* These aren't too critical.
|
|
* (they're not supposed to happen).
|
|
* In order to debug the reason for address exceptions in non-user modes,
|
|
* we have to obtain all the registers so that we can see what's going on.
|
|
*/
|
|
|
|
vector_addrexcptn:
|
|
sub lr, lr, #8
|
|
tst lr, #3
|
|
bne Laddrexcptn_not_user
|
|
save_user_regs
|
|
teq pc, #MODE_SVC26
|
|
mask_pc r0, lr @ Point to instruction
|
|
mov r1, sp @ Point to registers
|
|
mov r2, #0x400
|
|
mov lr, pc
|
|
bl do_excpt
|
|
b ret_from_exception
|
|
|
|
Laddrexcptn_not_user:
|
|
save_svc_regs
|
|
and r2, lr, #3
|
|
teq r2, #3
|
|
bne Laddrexcptn_illegal_mode
|
|
teqp pc, #MODE_SVC26
|
|
mask_pc r0, lr
|
|
mov r1, sp
|
|
orr r2, r2, #0x400
|
|
bl do_excpt
|
|
ldmia sp, {r0 - lr} @ I cant remember the reason I changed this...
|
|
add sp, sp, #15*4
|
|
movs pc, lr
|
|
|
|
Laddrexcptn_illegal_mode:
|
|
mov r0, sp
|
|
str lr, [sp, #-4]!
|
|
orr r1, r2, #PSR_I_BIT | PSR_F_BIT
|
|
teqp r1, #0 @ change into mode (wont be user mode)
|
|
mov r0, r0
|
|
mov r1, r8 @ Any register from r8 - r14 can be banked
|
|
mov r2, r9
|
|
mov r3, r10
|
|
mov r4, r11
|
|
mov r5, r12
|
|
mov r6, r13
|
|
mov r7, r14
|
|
teqp pc, #PSR_F_BIT | MODE_SVC26 @ back to svc
|
|
mov r0, r0
|
|
stmfd sp!, {r1-r7}
|
|
ldmia r0, {r0-r7}
|
|
stmfd sp!, {r0-r7}
|
|
mov r0, sp
|
|
mov r1, #BAD_ADDREXCPTN
|
|
b bad_mode
|
|
|
|
/*=============================================================================
|
|
* Interrupt (IRQ) handler
|
|
*-----------------------------------------------------------------------------
|
|
* Note: if the IRQ was taken whilst in user mode, then *no* kernel routine
|
|
* is running, so do not have to save svc lr.
|
|
*
|
|
* Entered in IRQ mode.
|
|
*/
|
|
|
|
vector_IRQ: ldr sp, .LCirq @ Setup some temporary stack
|
|
sub lr, lr, #4
|
|
str lr, [sp] @ push return address
|
|
|
|
tst lr, #3
|
|
bne __irq_non_usr
|
|
|
|
__irq_usr: teqp pc, #PSR_I_BIT | MODE_SVC26 @ Enter SVC mode
|
|
mov r0, r0
|
|
|
|
ldr lr, .LCirq
|
|
ldr lr, [lr] @ Restore lr for jump back to USR
|
|
|
|
save_user_regs
|
|
|
|
handle_irq
|
|
|
|
mov why, #0
|
|
get_thread_info tsk
|
|
b ret_to_user
|
|
|
|
@ Place the IRQ priority table here so that the handle_irq macros above
|
|
@ and below here can access it.
|
|
|
|
irq_prio_table
|
|
|
|
__irq_non_usr: teqp pc, #PSR_I_BIT | MODE_SVC26 @ Enter SVC mode
|
|
mov r0, r0
|
|
|
|
save_svc_regs_irq
|
|
|
|
and r2, lr, #3
|
|
teq r2, #3
|
|
bne __irq_invalid @ IRQ not from SVC mode
|
|
|
|
handle_irq
|
|
|
|
restore_svc_regs
|
|
|
|
__irq_invalid: mov r0, sp
|
|
mov r1, #BAD_IRQ
|
|
b bad_mode
|
|
|
|
/*=============================================================================
|
|
* Data abort handler code
|
|
*-----------------------------------------------------------------------------
|
|
*
|
|
* This handles both exceptions from user and SVC modes, computes the address
|
|
* range of the problem, and does any correction that is required. It then
|
|
* calls the kernel data abort routine.
|
|
*
|
|
* This is where I wish that the ARM would tell you which address aborted.
|
|
*/
|
|
|
|
vector_data: sub lr, lr, #8 @ Correct lr
|
|
tst lr, #3
|
|
bne Ldata_not_user
|
|
save_user_regs
|
|
teqp pc, #MODE_SVC26
|
|
mask_pc r0, lr
|
|
bl Ldata_do
|
|
b ret_from_exception
|
|
|
|
Ldata_not_user:
|
|
save_svc_regs
|
|
and r2, lr, #3
|
|
teq r2, #3
|
|
bne Ldata_illegal_mode
|
|
tst lr, #PSR_I_BIT
|
|
teqeqp pc, #MODE_SVC26
|
|
mask_pc r0, lr
|
|
bl Ldata_do
|
|
restore_svc_regs
|
|
|
|
Ldata_illegal_mode:
|
|
mov r0, sp
|
|
mov r1, #BAD_DATA
|
|
b bad_mode
|
|
|
|
Ldata_do: mov r3, sp
|
|
ldr r4, [r0] @ Get instruction
|
|
mov r2, #0
|
|
tst r4, #1 << 20 @ Check to see if it is a write instruction
|
|
orreq r2, r2, #FAULT_CODE_WRITE @ Indicate write instruction
|
|
mov r1, r4, lsr #22 @ Now branch to the relevent processing routine
|
|
and r1, r1, #15 << 2
|
|
add pc, pc, r1
|
|
movs pc, lr
|
|
b Ldata_unknown
|
|
b Ldata_unknown
|
|
b Ldata_unknown
|
|
b Ldata_unknown
|
|
b Ldata_ldrstr_post @ ldr rd, [rn], #m
|
|
b Ldata_ldrstr_numindex @ ldr rd, [rn, #m] @ RegVal
|
|
b Ldata_ldrstr_post @ ldr rd, [rn], rm
|
|
b Ldata_ldrstr_regindex @ ldr rd, [rn, rm]
|
|
b Ldata_ldmstm @ ldm*a rn, <rlist>
|
|
b Ldata_ldmstm @ ldm*b rn, <rlist>
|
|
b Ldata_unknown
|
|
b Ldata_unknown
|
|
b Ldata_ldrstr_post @ ldc rd, [rn], #m @ Same as ldr rd, [rn], #m
|
|
b Ldata_ldcstc_pre @ ldc rd, [rn, #m]
|
|
b Ldata_unknown
|
|
Ldata_unknown: @ Part of jumptable
|
|
mov r0, r1
|
|
mov r1, r4
|
|
mov r2, r3
|
|
b baddataabort
|
|
|
|
Ldata_ldrstr_post:
|
|
mov r0, r4, lsr #14 @ Get Rn
|
|
and r0, r0, #15 << 2 @ Mask out reg.
|
|
teq r0, #15 << 2
|
|
ldr r0, [r3, r0] @ Get register
|
|
biceq r0, r0, #PCMASK
|
|
mov r1, r0
|
|
#ifdef FAULT_CODE_LDRSTRPOST
|
|
orr r2, r2, #FAULT_CODE_LDRSTRPOST
|
|
#endif
|
|
b do_DataAbort
|
|
|
|
Ldata_ldrstr_numindex:
|
|
mov r0, r4, lsr #14 @ Get Rn
|
|
and r0, r0, #15 << 2 @ Mask out reg.
|
|
teq r0, #15 << 2
|
|
ldr r0, [r3, r0] @ Get register
|
|
mov r1, r4, lsl #20
|
|
biceq r0, r0, #PCMASK
|
|
tst r4, #1 << 23
|
|
addne r0, r0, r1, lsr #20
|
|
subeq r0, r0, r1, lsr #20
|
|
mov r1, r0
|
|
#ifdef FAULT_CODE_LDRSTRPRE
|
|
orr r2, r2, #FAULT_CODE_LDRSTRPRE
|
|
#endif
|
|
b do_DataAbort
|
|
|
|
Ldata_ldrstr_regindex:
|
|
mov r0, r4, lsr #14 @ Get Rn
|
|
and r0, r0, #15 << 2 @ Mask out reg.
|
|
teq r0, #15 << 2
|
|
ldr r0, [r3, r0] @ Get register
|
|
and r7, r4, #15
|
|
biceq r0, r0, #PCMASK
|
|
teq r7, #15 @ Check for PC
|
|
ldr r7, [r3, r7, lsl #2] @ Get Rm
|
|
and r8, r4, #0x60 @ Get shift types
|
|
biceq r7, r7, #PCMASK
|
|
mov r9, r4, lsr #7 @ Get shift amount
|
|
and r9, r9, #31
|
|
teq r8, #0
|
|
moveq r7, r7, lsl r9
|
|
teq r8, #0x20 @ LSR shift
|
|
moveq r7, r7, lsr r9
|
|
teq r8, #0x40 @ ASR shift
|
|
moveq r7, r7, asr r9
|
|
teq r8, #0x60 @ ROR shift
|
|
moveq r7, r7, ror r9
|
|
tst r4, #1 << 23
|
|
addne r0, r0, r7
|
|
subeq r0, r0, r7 @ Apply correction
|
|
mov r1, r0
|
|
#ifdef FAULT_CODE_LDRSTRREG
|
|
orr r2, r2, #FAULT_CODE_LDRSTRREG
|
|
#endif
|
|
b do_DataAbort
|
|
|
|
Ldata_ldmstm:
|
|
mov r7, #0x11
|
|
orr r7, r7, r7, lsl #8
|
|
and r0, r4, r7
|
|
and r1, r4, r7, lsl #1
|
|
add r0, r0, r1, lsr #1
|
|
and r1, r4, r7, lsl #2
|
|
add r0, r0, r1, lsr #2
|
|
and r1, r4, r7, lsl #3
|
|
add r0, r0, r1, lsr #3
|
|
add r0, r0, r0, lsr #8
|
|
add r0, r0, r0, lsr #4
|
|
and r7, r0, #15 @ r7 = no. of registers to transfer.
|
|
mov r5, r4, lsr #14 @ Get Rn
|
|
and r5, r5, #15 << 2
|
|
ldr r0, [r3, r5] @ Get reg
|
|
eor r6, r4, r4, lsl #2
|
|
tst r6, #1 << 23 @ Check inc/dec ^ writeback
|
|
rsbeq r7, r7, #0
|
|
add r7, r0, r7, lsl #2 @ Do correction (signed)
|
|
subne r1, r7, #1
|
|
subeq r1, r0, #1
|
|
moveq r0, r7
|
|
tst r4, #1 << 21 @ Check writeback
|
|
strne r7, [r3, r5]
|
|
eor r6, r4, r4, lsl #1
|
|
tst r6, #1 << 24 @ Check Pre/Post ^ inc/dec
|
|
addeq r0, r0, #4
|
|
addeq r1, r1, #4
|
|
teq r5, #15*4 @ CHECK FOR PC
|
|
biceq r1, r1, #PCMASK
|
|
biceq r0, r0, #PCMASK
|
|
#ifdef FAULT_CODE_LDMSTM
|
|
orr r2, r2, #FAULT_CODE_LDMSTM
|
|
#endif
|
|
b do_DataAbort
|
|
|
|
Ldata_ldcstc_pre:
|
|
mov r0, r4, lsr #14 @ Get Rn
|
|
and r0, r0, #15 << 2 @ Mask out reg.
|
|
teq r0, #15 << 2
|
|
ldr r0, [r3, r0] @ Get register
|
|
mov r1, r4, lsl #24 @ Get offset
|
|
biceq r0, r0, #PCMASK
|
|
tst r4, #1 << 23
|
|
addne r0, r0, r1, lsr #24
|
|
subeq r0, r0, r1, lsr #24
|
|
mov r1, r0
|
|
#ifdef FAULT_CODE_LDCSTC
|
|
orr r2, r2, #FAULT_CODE_LDCSTC
|
|
#endif
|
|
b do_DataAbort
|
|
|
|
|
|
/*
|
|
* This is the return code to user mode for abort handlers
|
|
*/
|
|
ENTRY(ret_from_exception)
|
|
get_thread_info tsk
|
|
mov why, #0
|
|
b ret_to_user
|
|
|
|
.data
|
|
ENTRY(fp_enter)
|
|
.word fpe_not_present
|
|
.text
|
|
/*
|
|
* Register switch for older 26-bit only ARMs
|
|
*/
|
|
ENTRY(__switch_to)
|
|
add r0, r0, #TI_CPU_SAVE
|
|
stmia r0, {r4 - sl, fp, sp, lr}
|
|
add r1, r1, #TI_CPU_SAVE
|
|
ldmia r1, {r4 - sl, fp, sp, pc}^
|
|
|
|
/*
|
|
*=============================================================================
|
|
* Low-level interface code
|
|
*-----------------------------------------------------------------------------
|
|
* Trap initialisation
|
|
*-----------------------------------------------------------------------------
|
|
*
|
|
* Note - FIQ code has changed. The default is a couple of words in 0x1c, 0x20
|
|
* that call _unexp_fiq. Nowever, we now copy the FIQ routine to 0x1c (removes
|
|
* some excess cycles).
|
|
*
|
|
* What we need to put into 0-0x1c are branches to branch to the kernel.
|
|
*/
|
|
|
|
.section ".init.text",#alloc,#execinstr
|
|
|
|
.Ljump_addresses:
|
|
swi SYS_ERROR0
|
|
.word vector_undefinstr - 12
|
|
.word vector_swi - 16
|
|
.word vector_prefetch - 20
|
|
.word vector_data - 24
|
|
.word vector_addrexcptn - 28
|
|
.word vector_IRQ - 32
|
|
.word _unexp_fiq - 36
|
|
b . + 8
|
|
/*
|
|
* initialise the trap system
|
|
*/
|
|
ENTRY(__trap_init)
|
|
stmfd sp!, {r4 - r7, lr}
|
|
adr r1, .Ljump_addresses
|
|
ldmia r1, {r1 - r7, ip, lr}
|
|
orr r2, lr, r2, lsr #2
|
|
orr r3, lr, r3, lsr #2
|
|
orr r4, lr, r4, lsr #2
|
|
orr r5, lr, r5, lsr #2
|
|
orr r6, lr, r6, lsr #2
|
|
orr r7, lr, r7, lsr #2
|
|
orr ip, lr, ip, lsr #2
|
|
mov r0, #0
|
|
stmia r0, {r1 - r7, ip}
|
|
ldmfd sp!, {r4 - r7, pc}^
|
|
|
|
.bss
|
|
__temp_irq: .space 4 @ saved lr_irq
|
|
__temp_fiq: .space 128
|