/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#define	ASSEMBLY_SOURCE
#include "kernel.h"
#include <chips/ARM/defs.h>
#include <chips/ARM/func.h>

#define	KEVENTS	(ARM_STATE_SYS|ARM_IRQDI_MASK)
#define	HANDLER	(ARM_STATE_IRQ|ARM_IRQDI_MASK)

	.globl	BX_IRQ_Entry
	.globl	BX_IRQ_Handler
	.globl	_bx_IRQ_Fake
	.globl	_bx_EventFlush
	.globl	_bx_state

/*
*	This is the IRQ exception handler
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	When an interrupt enters, first the registers that GCC does not save
*	(R0-R3, R12) and the return address are pushed to the interrupt stack.
*	The the user's ISR is called, but a kernel state flag is set to indicate
*	that we're servicing an interrupt.
*FIXME
*								Otherwise, if it interrupted a user process,
*	it checks if the event queue length is zero or not. If it is zero, then
*	the ISR had no effect on the system state, therefore the routine simply
*	returns. If the event queue contains events to be serviced, then the
*	kernel's event queue handler is invoked.
*FIXME
*	If not, then the current context's registers are restored and the routine
*	returns. Otherwise, the current register set as well as the ones saved
*	on the stack are stored in the old context, then the new context is loaded
*	and the routine returns to the new context.
*
*	Note that this is the single point where the kernel switches contexts.
*	If contex switch is caused by an user level kernel call, then the user
*	level call switches the processor into IRQ state, builds a fake IRQ
*	stack frame and jumps into the middle of this routine. In effect, user
*	invoked kernel calls are like software-triggered IRQs, but with a lot
*	less overhead than a full-blown IRQ ISR would be (in particular, the
*	actual ISR does not exist, since all that was to be done has already
*	been processed in user mode).
*
*	IMPORTANT:
*		The number of pushed/popped registers MUST BE even. EABI
*		requires the stack to be 8-byte aligned, which it is at
*		entry and we must keep it that way when we call a handler.
*/
	.align	2
	.code	32

BX_IRQ_Entry:
	sub		lr,lr,#4					// Adjust the return address
	stmdb	sp!,{r0-r5,r12,lr}			// Save registers not saved by gcc + 2
	mrs		r0,spsr						// Get the user's status reg
	tst		r0,#ARM_IRQDI_MASK			// See if we have a stray IRQ
	ldmneia	sp!,{r0-r5,r12,pc}^			// If yes, restore registers and return
	ldr		r4,pointers					// Get the kernel state pointer
	ldr		r12,[r4,#BX_EXEC_ISR*4]		// Get the address of the user's handler
	add		r0,r4,#BX_EXEC_MOD*4		// Address of the system mode word
	mov		r5,#BX_MODE_IRQ				// We'll be in interrupt mode
	swp		r5,r5,[r0]					// Fetch old mode and store the new
	callreg	r12							// Call the user's function
	str		r5,[r4,#BX_EXEC_MOD*4]		// Restore old mode
	cmp		r5,#BX_MODE_SYS				// Check if we interrupted the kernel
	ldmeqia	sp!,{r0-r5,r12,pc}^			// If yes, restore registers and return
	ldr		r0,[r4,#BX_EXEC_LEN*4]		// Get the event queue length
	tst		r0,r0						// Is it 0?
	ldmeqia	sp!,{r0-r5,r12,pc}^			// If yes, restore registers and return
	mrs		r0,spsr						// Save the SPSR
flush_events:
	sub		sp,sp,#12					// Make space on the IRQ stack
	stmia	sp,{r0,sp,lr}^				// Save shadow registers and spsr in R0
	msr		cpsr,#KEVENTS				// Switch to system mode (new stack!)
	ldr		sp,[r4,#BX_EXEC_STK*4]		// Load the kernel's stack
	mov		r5,#BX_MODE_SYS				// Indicates that we're in kernel mode
	str		r5,[r4,#BX_EXEC_MOD*4]		// Store mode in the state
	ldr		r12,pointers+4				// Get address of the event handler
	callreg	r12							// Call it
	msr		cpsr,#HANDLER				// Switch back to IRQ mode
	ldmia	sp,{r0,sp,lr}^				// Restore shadow registers and R0
	msr		spsr,r0						// Restore the SPSR from R0
	add		sp,sp,#12					// Restore IRQ stack pointer
	mov		r0,#BX_MODE_USR				// We will now return to user mode
	str		r0,[r4,#BX_EXEC_MOD*4]		// Indicate the new mode
	ldr		r0,[r4,#BX_EXEC_OLD*4]		// Get the old context pointer
	ldr		r1,[r4,#BX_EXEC_NEW*4]		// Get the new context pointer
	cmp		r0,r1						// Are they the same?
	ldmeqia	sp!,{r0-r5,r12,pc}^			// If yes, restore registers and return
	str		r1,[r4,#BX_EXEC_OLD*4]		// Indicate that we switched context
	add		r2,r0,#BX_CNTX_R6*4			// R2 now points to R6 in old context
	stmia	r2!,{r6-r11}				// Save R6-R11 in old context
	ldmia	sp!,{r5-r12}				// Fetch R0-R5,R12,PC from the stack
	stmia	r0,{r5-r10}					// Save R0-R5 in old context
	stmia	r2,{r11,sp,lr}^				// Save R12-R14 of old context
	mov		r11,r12						// Move old PC to R11
	mrs		r12,spsr					// Get the user's status register
	add		r2,r0,#15*4					// Adjust R2 to point to the PC
	stmia	r2,{r11,r12}				// Store PC, SR in old context
	mov		lr,r1						// Move the new context pointer to LR
load_new:
	ldr		r0,[lr,#BX_CNTX_PS*4]		// Get new PS
	msr		spsr,r0						// Move it to its place
	ldmia	lr,{r0-lr}^					// Load R0-R14 of new context
	nop									// Needed because ldmia uses LR^
	ldr		lr,[lr,#BX_CNTX_PC*4]		// Get the new PC
	movs	pc,lr						// Return to the new context

/*
*	This is the 'fake' interrupt that switches to IRQ mode
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It flushes the event queue and, if necessary, executes a context switch.
*	It jumps into the kernel's IRQ handler, when it switches to system mode
*	and starts the event queue handler.
*	When we get there the following should be true:
*	- the processor is in IRQ mode
*	- R0-R5,R12 and the return address are pushed onto the IRQ stack
*	- R0 contains a copy of SPSR (and SPSR doesn't have to be valid any more)
*	- R4 contains a pointer to the kernel state
*
*	Since this function is called from C, we're free to destroy R0-R3
*	and R12, of which we indeed destroy R0 and R12.
*	It goes without saying that this function *must* be called while
*	interrupt is disabled.
*
*	Note that copying LR into R12 is not without a reason. We will switch
*	into IRQ mode before pushing the registers onto the stack. At that
*	point LR will also be switched and we need to push the system mode
*	LR, not the IRQ LR. Thus, we move the system mode LR into R12 and
*	we push R0-R5 (which will be R0-R5 when popped), R6 (which will be
*	R12 when popped) and R12 (which will be LR when popped).
*/

	afunc	_bx_QueueClear
	mov		r12,lr						// We'll need LR later
	mrs		r0,cpsr						// Get the current status word
	tst		lr,#1						// Was the caller in THUMB mode?
	orrne	r0,r0,#ARM_THUMB_MASK		// If yes, set the THUMB bit in CPSR
	msr		cpsr,#HANDLER				// Switch to IRQ mode
	stmdb	sp!,{r0-r6,r12}				// Create the fake IRQ stack frame
	ldr		r4,pointers					// Get the kernel state addr.
	b		flush_events				// Flush the event queue
	.endfunc

/*
*	This is the starting point of the kernel
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	We are called from interrupt state, interrupts are disabled.
*	R0 contains a pointer to the new context that should be loaded.
*/

	afunc	_bx_ProcLoad
	mov		lr,r0						// Get the context pointer to its place
	b		load_new					// Load the new context
	.endfunc

/*
*	This function is disables the interrupt in the SPSR
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	It is called from the kernel's event queue put routine
*	when it detects that the queue gets over the watermark.
*/

	afunc	_bx_SpsrDis
	mrs		r0,cpsr						// Get current status
	and		r0,r0,#ARM_STATE_MASK		// Current status
	cmp		r0,#ARM_STATE_IRQ			// Are we in in IRQ handler ?
	bxne	lr							// If not, nothing to do
	mrs		r0,spsr						// Get the SPSR
	orr		r0,r0,#ARM_IRQDI_MASK		// Set the IRQ disable flag
	msr		spsr,r0						// Restore the modified SPSR
	bx		lr							// and return

/*
*	Data used by the above functions
*/

pointers:
	.word	_bx_state					// Kernel state structure address
	.word	_bx_QueueFlush				// Event flushing function

