/* -*- mode: asm -*-
 *
 *  linux/arch/m68knommu/platform/5307/entry.S
 *
 *  Copyright (C) 1999-2002, Greg Ungerer (gerg@snapgear.com)
 *  Copyright (C) 1998  D. Jeff Dionne <jeff@lineo.ca>,
 *                      Kenneth Albanowski <kjahds@kjahds.com>,
 *  Copyright (C) 2000  Lineo Inc. (www.lineo.com) 
 *
 * Based on:
 *
 *  linux/arch/m68k/kernel/entry.S
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file README.legal in the main directory of this archive
 * for more details.
 *
 * Linux/m68k support by Hamish Macdonald
 *
 * 68060 fixes by Jesper Skov
 * ColdFire support by Greg Ungerer (gerg@snapgear.com)
 * 5307 fixes by David W. Miller
 * linux 2.4 support David McCullough <davidm@snapgear.com>
 */

/*
 * entry.S  contains the system-call and fault low-level handling routines.
 * This also contains the timer-interrupt handler, as well as all interrupts
 * and faults that can result in a task-switch.
 *
 * NOTE: This code handles signal-recognition, which happens every time
 * after a timer-interrupt and after each system call.
 *
 * Stack layout in 'ret_from_exception':
 *
 *	This allows access to the syscall arguments in registers d1-d5
 *
 *	 0(sp) - d1
 *	 4(sp) - d2
 *	 8(sp) - d3
 *	 C(sp) - d4
 *	10(sp) - d5
 *	14(sp) - a0
 *	18(sp) - a1
 *	1C(sp) - a2
 *	20(sp) - d0
 *	24(sp) - orig_d0
 *	28(sp) - stack adjustment
 *	2C(sp) - format & vector      }
 *	2E(sp) - sr                   } different to m68k
 *	30(sp) - pc                   }
 */

#include <linux/config.h>
#include <linux/sys.h>
#include <linux/linkage.h>
#include <asm/thread_info.h>
#include <asm/errno.h>
#include <asm/setup.h>
#include <asm/segment.h>
#include <asm/asm-offsets.h>

LENOSYS = 38

/* the following macro is used when enabling interrupts */
#define ALLOWINT 0xf8ff
#define	MAX_NOINT_IPL	0


LD0		= 0x20
LORIG_D0	= 0x24
LFORMATVEC	= 0x2c
LSR		= 0x2e
LPC		= 0x30

/*
 * This defines the normal kernel pt-regs layout.
 *
 * regs are a2-a6 and d6-d7 preserved by C code
 * the kernel doesn't mess with usp unless it needs to
 *
 *	This is made a little more tricky on the ColdFire. There is no
 *	separate kernel and user stack pointers. Need to artificially
 *	construct a usp in software... When doing this we need to disable
 *	interrupts, otherwise bad things could happen.
 */
#define SAVE_ALL						\
	move	#0x2700,%sr;		/* disable intrs */	\
	btst	#5,%sp@(2);		/* from user? */	\
	bnes	6f;			/* no, skip */		\
	movel	%sp,sw_usp;		/* save user sp */	\
	addql	#8,sw_usp;		/* remove exception */	\
	movel	sw_ksp,%sp;		/* kernel sp */		\
	subql	#8,%sp;			/* room for exception */\
	clrl	%sp@-;			/* stk_adj */		\
	movel	%d0,%sp@-;		/* orig d0 */		\
	movel	%d0,%sp@-;		/* d0 */		\
	subl	#32,%sp;		/* space for 8 regs */	\
	moveml	%d1-%d5/%a0-%a2,%sp@;				\
	movel	sw_usp,%a0;		/* get usp */		\
	moveml	%a0@(-8),%d1-%d2;	/* get exception */	\
	moveml	%d1-%d2,%sp@(LFORMATVEC); /* copy exception */	\
	bra	7f;						\
	6:							\
	clrl	%sp@-;			/* stk_adj */		\
	movel	%d0,%sp@-;		/* orig d0 */		\
	movel	%d0,%sp@-;		/* d0 */		\
	subl	#32,%sp;		/* space for 7 regs */	\
	moveml	%d1-%d5/%a0-%a2,%sp@;				\
	7:

#define RESTORE_ALL						\
	btst	#5,%sp@(LSR);		/* going user? */	\
	bnes	8f;			/* no, skip */		\
	move	#0x2700,%sr;		/* disable intrs */	\
	movel	sw_usp,%a0;		/* get usp */		\
	moveml	%sp@(LFORMATVEC),%d1-%d2; /* copy exception */	\
	moveml	%d1-%d2,%a0@(-8);				\
	moveml	%sp@,%d1-%d5/%a0-%a2;				\
	addl	#32,%sp;		/* space for 8 regs */	\
	movel	%sp@+,%d0;					\
	addql	#4,%sp;			/* orig d0 */		\
	addl	%sp@+,%sp;		/* stk adj */		\
	addql	#8,%sp;			/* remove exception */	\
	movel	%sp,sw_ksp;		/* save ksp */		\
	subql	#8,sw_usp;		/* set exception */	\
	movel	sw_usp,%sp;		/* restore usp */	\
	rte;							\
	8:							\
	moveml	%sp@,%d1-%d5/%a0-%a2;				\
	addl	#32,%sp;		/* space for 8 regs */	\
	movel	%sp@+,%d0;					\
	addql	#4,%sp;			/* orig d0 */		\
	addl	%sp@+,%sp;		/* stk adj */		\
	rte

/*
 *	Quick exception save, use current stack only.
 */
#define SAVE_LOCAL						\
	move	#0x2700,%sr;		/* disable intrs */	\
	clrl	%sp@-;			/* stk_adj */		\
	movel	%d0,%sp@-;		/* orig d0 */		\
	movel	%d0,%sp@-;		/* d0 */		\
	subl	#32,%sp;		/* space for 8 regs */	\
	moveml	%d1-%d5/%a0-%a2,%sp@;

#define RESTORE_LOCAL						\
	moveml	%sp@,%d1-%d5/%a0-%a2;				\
	addl	#32,%sp;		/* space for 8 regs */	\
	movel	%sp@+,%d0;					\
	addql	#4,%sp;			/* orig d0 */		\
	addl	%sp@+,%sp;		/* stk adj */		\
	rte


#define SWITCH_STACK_SIZE (6*4+4)	/* includes return address */

#define SAVE_SWITCH_STACK		\
	subl    #24,%sp; /* 6 regs */	\
	moveml	%a3-%a6/%d6-%d7,%sp@

#define RESTORE_SWITCH_STACK		\
	moveml	%sp@,%a3-%a6/%d6-%d7;	\
	addl	#24,%sp /* 6 regs */

/*
 *	Software copy of the user and kernel stack pointers... Ugh...
 *	Need these to get around ColdFire not having separate kernel
 *	and user stack pointers.
 */
.globl sw_usp
.globl sw_ksp

.data

sw_ksp:
.long	0

sw_usp:
.long	0

.text


.globl buserr
.globl trap
.globl system_call
.globl resume, ret_from_exception
.globl ret_from_signal
.globl sys_call_table
.globl sys_fork, sys_clone, sys_vfork
.globl ret_from_interrupt
.globl inthandler
.globl fasthandler

#ifdef TRAP_DBG_INTERRUPT
.globl dbginterrupt
#endif

/*--------------------------------------------------------------------------*/

.text

ENTRY(buserr)
	SAVE_ALL
	moveq	#-1,%d0
	movel	%d0,%sp@(LORIG_D0)	| a -1 in the ORIG_D0 field
					| signifies that the stack frame
					| is NOT for syscall

	movel	%sp,%sp@- 		| stack frame pointer argument
	jsr	buserr_c
	addql	#4,%sp
	jra	ret_from_exception


#ifdef TRAP_DBG_INTERRUPT
ENTRY(dbginterrupt)
	SAVE_ALL
	moveq	#-1,%d0
	movel	%d0,%sp@(LORIG_D0)	| a -1 in the ORIG_D0 field
					| signifies that the stack frame
					| is NOT for syscall

	movel	%sp,%sp@- 		| stack frame pointer argument
	jsr	dbginterrupt_c
	addql	#4,%sp
	jra	ret_from_exception
#endif


ENTRY(reschedule)
	| save top of frame
	pea	%sp@
	jbsr	set_esp0
	addql	#4,%sp

	pea	ret_from_exception
	jmp	schedule

	| After a fork we jump here directly from resume,
	| so that %d1 contains the previous task
	| Theoretically only needed on SMP, but lets watch
	| what happens in schedule_tail() in future...
ENTRY(ret_from_fork)
#ifdef CONFIG_SMP
	movel	%d1,%sp@-
	jsr	schedule_tail
	addql	#4,%sp
#endif
	jra	ret_from_exception

ENTRY(system_call)
	SAVE_ALL
	move	#0x2000,%sr;		| enable intrs again

	movel	#-LENOSYS,%d2
	movel	%d2,LD0(%sp)		| default return value in d0
					| original D0 is in orig_d0
	movel	%d0,%d2

	| save top of frame
	pea	%sp@
	jbsr	set_esp0
	addql	#4,%sp

	cmpl	#NR_syscalls,%d2
	jcc	ret_from_exception
	lea	sys_call_table,%a0
	lsll	#2,%d2			| movel	%a0@(%d2:l:4),%d3
	movel	%a0@(%d2),%d3
	jeq	ret_from_exception
	lsrl	#2,%d2

	movel	%sp,%d2			| get thread_info pointer
	andl	#0xffffe000,%d2		| at start of 8k kernel stack
	movel	%d2,%a0
	btst	#TIF_SYSCALL_TRACE,%a0@(TI_FLAGS)
	bnes	1f

	movel	%d3,%a0
	jbsr	%a0@
	movel	%d0,%sp@(LD0)		| save the return value
	jra	ret_from_exception
1:
	subql	#4,%sp
	SAVE_SWITCH_STACK
	jbsr	syscall_trace
	RESTORE_SWITCH_STACK
	addql	#4,%sp
	movel	%d3,%a0
	jbsr	%a0@
	movel	%d0,%sp@(LD0)		| save the return value
	subql	#4,%sp			| dummy return address
	SAVE_SWITCH_STACK
	jbsr	syscall_trace

ret_from_signal:
	RESTORE_SWITCH_STACK
	addql	#4,%sp

ret_from_exception:
	btst	#5,%sp@(LSR)		| check if returning to kernel
	jeq	Luser_return		| if so, skip resched, signals

Lkernel_return:
	moveml  %sp@,%d1-%d5/%a0-%a2
	addl    #32,%sp			| space for 8 regs
	movel   %sp@+,%d0
	addql   #4,%sp			| orig d0
	addl    %sp@+,%sp		| stk adj
	rte

Luser_return:
	movel	%sp,%d1			| get thread_info pointer
	andl	#0xffffe000,%d1		| at base of 8k kernel stack
	movel	%d1,%a0
	movel	%a0@(TI_FLAGS),%d1	| get thread_info->flags
	andl	#_TIF_WORK_MASK,%d1
	jne	Lwork_to_do		| still work to do

Lreturn:
	move    #0x2700,%sr		| disable intrs
	movel   sw_usp,%a0		| get usp
	moveml  %sp@(LFORMATVEC),%d1-%d2 | copy exception
	moveml  %d1-%d2,%a0@(-8)
	bclr    #5,%a0@(-8)		| clear format byte, bit 5 to make
					| stack appear modulo 4 which it WILL
					| be when we do the rte because it was
					| generated in setup_frame
	bclr    #4,%a0@(-8)		| clear format byte, bit 4 to make 
					| stack appear modulo 4 which it WILL
					| be when we do the rte because it was
					| generated in setup_frame
	moveml  %sp@,%d1-%d5/%a0-%a2
	addl    #32,%sp			| space for 8 regs
	movel   %sp@+,%d0                  
	addql   #4,%sp			| orig d0
	addl    %sp@+,%sp		| stk adj
	addql   #8,%sp			| remove exception
	movel   %sp,sw_ksp		| save ksp
	movel   sw_usp,%sp		| restore usp
	subql   #8,%sp			| set exception
	rte

Lwork_to_do:
	movel	%a0@(TI_FLAGS),%d1	| get thread_info->flags
	btst	#TIF_NEED_RESCHED,%d1
	jne	reschedule

	/* GERG: do we need something here for TRACEing?? */

Lsignal_return:
	subql	#4,%sp			| dummy return address
	SAVE_SWITCH_STACK
	pea	%sp@(SWITCH_STACK_SIZE)
	clr	%d1
	movel	%d1,%sp@-
	jsr	do_signal
	addql	#8,%sp
	RESTORE_SWITCH_STACK
	addql	#4,%sp
	jmp	Lreturn

/*--------------------------------------------------------------------------*/

/*
 *	Common ColdFire trap handler. Most traps come through here first.
 */
ENTRY(trap)
	SAVE_ALL
	moveq	#-1,%d0
	movel	%d0,%sp@(LORIG_D0)	| a -1 in the ORIG_D0 field
					| signifies that the stack frame
					| is NOT for syscall
	movel	%sp,%sp@- 		| stack frame pointer argument
	jsr	trap_c
	addql	#4,%sp
	jra	ret_from_exception

/*
 *	This is the generic interrupt handler (for all hardware interrupt
 *	sources). It figures out the vector number and calls the appropriate
 *	interrupt service routine directly.
 */
ENTRY(inthandler)
	SAVE_ALL
	moveq	#-1,%d0
	movel	%d0,%sp@(LORIG_D0)	| a -1 in the ORIG_D0 field
					| signifies that the stack frame
					| is NOT for syscall
	addql	#1,local_irq_count
					| put exception # in d0
	movew   %sp@(LFORMATVEC),%d0
	andl	#0x03fc,%d0		| mask out vector only

	movel	mach_kstat_irqs,%a0
					| get addr of kstat struct
	addql	#1,%a0@(%d0)		| incr irq intr count

	lsrl	#2,%d0			| calculate real vector #
	movel	%d0,%d1			| calculate array offset
	lsll	#4,%d1
	lea	irq_list,%a0
	addl	%d1,%a0			| pointer to array struct

	movel	%sp,%sp@-		| push regs arg onto stack
	movel	%a0@(8),%sp@-		| push devid arg
	movel	%d0,%sp@- 		| push vector # on stack

	movel	%a0@,%a0		| get function to call
	jbsr	%a0@			| call vector handler
	addl	#12,%sp			| pop parameters off stack

	bra	ret_from_interrupt	| this was fallthrough


/*
 *	This is the fast interrupt handler (for certain hardware interrupt
 *	sources). Unlike the normal interrupt handler it just uses the
 *	current stack (doesn't care if it is user or kernel). It also
 *	doesn't bother doing the bottom half handlers.
 */
ENTRY(fasthandler)
	SAVE_LOCAL

	movew   %sp@(LFORMATVEC),%d0
	andl	#0x03fc,%d0		| mask out vector only

	movel	mach_kstat_irqs,%a0
					| get addr of kstat struct
	addql	#1,%a0@(%d0)		| incr irq intr count

	movel	%sp,%sp@-		| push regs arg onto stack
	clrl	%d1
	movel	%d1,%sp@- 		| push devid arg
	lsrl	#2,%d0			| calculate real vector #
	movel	%d0,%sp@- 		| push vector # on stack

	lsll	#4,%d0			| adjust for array offset
	lea	irq_list,%a0
	movel	%a0@(%d0),%a0		| get function to call
	jbsr	%a0@			| call vector handler
	addl	#12,%sp			| pop parameters off stack

	RESTORE_LOCAL

/*--------------------------------------------------------------------------*/

ENTRY(ret_from_interrupt)
	subql	#1,local_irq_count
	jeq	2f
1:
	RESTORE_ALL
2:
	moveb	%sp@(LSR),%d0
	andl	#0x7,%d0
#if MAX_NOINT_IPL > 0
	cmpiw	#MAX_NOINT_IPL,%d0
#endif
	jhi	1b

	/* check if we need to do software interrupts */
	movel	irq_stat+CPUSTAT_SOFTIRQ_PENDING,%d0
	jeq	ret_from_exception

	pea	ret_from_exception
	jmp	do_softirq

ENTRY(sys_fork)
	SAVE_SWITCH_STACK
	pea	%sp@(SWITCH_STACK_SIZE)
	jbsr	m68k_fork
	addql	#4,%sp
	RESTORE_SWITCH_STACK
	rts

ENTRY(sys_vfork)
	SAVE_SWITCH_STACK
	pea	%sp@(SWITCH_STACK_SIZE)
	jbsr	m68k_vfork
	addql	#4,%sp
	RESTORE_SWITCH_STACK
	rts

ENTRY(sys_clone)
	SAVE_SWITCH_STACK
	pea	%sp@(SWITCH_STACK_SIZE)
	jbsr	m68k_clone
	addql	#4,%sp
	RESTORE_SWITCH_STACK
	rts

ENTRY(sys_sigsuspend)
	SAVE_SWITCH_STACK
	pea	%sp@(SWITCH_STACK_SIZE)
	jbsr	do_sigsuspend
	addql	#4,%sp
	RESTORE_SWITCH_STACK
	rts

ENTRY(sys_rt_sigsuspend)
	SAVE_SWITCH_STACK
	pea	%sp@(SWITCH_STACK_SIZE)
	jbsr	do_rt_sigsuspend
	addql	#4,%sp
	RESTORE_SWITCH_STACK
	rts

ENTRY(sys_sigreturn)
	SAVE_SWITCH_STACK
	jbsr	do_sigreturn
	RESTORE_SWITCH_STACK
	rts

ENTRY(sys_rt_sigreturn)
	SAVE_SWITCH_STACK
	jbsr	do_rt_sigreturn
	RESTORE_SWITCH_STACK
	rts

/*--------------------------------------------------------------------------*/

/*
 *	Beware - when entering resume, prev (the current task) is
 *	in a0, next (the new task) is in a1,so don't change these
 *	registers until their contents are no longer needed.
 */
ENTRY(resume)

	movel	%a0, %d1			| get prev thread in d1

	movew	%sr,%d0				| save thread status reg
	movew	%d0,%a0@(TASK_THREAD+THREAD_SR)

	oril	#0x700,%d0			| disable interrupts
	move	%d0,%sr

	movel	sw_usp,%d0			| save usp
	movel	%d0,%a0@(TASK_THREAD+THREAD_USP)

	SAVE_SWITCH_STACK
	movel	%sp,%a0@(TASK_THREAD+THREAD_KSP) | save kernel stack pointer
	movel	%a1@(TASK_THREAD+THREAD_KSP),%sp | restore new thread stack
	RESTORE_SWITCH_STACK

	movel	%a1@(TASK_THREAD+THREAD_USP),%a0 | restore thread user stack
	movel	%a0, sw_usp

	movew	%a1@(TASK_THREAD+THREAD_SR),%d0	| restore thread status reg
	movew	%d0, %sr
	rts

/*--------------------------------------------------------------------------*/
