/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

/**
 * Thread Switching Functions for the Cortex M0 & M1
 *
 * Use the EABI calling standard (ARM's AAPCS) - Save r4 - r11
 * The context is saved at the current stack location and a pointer is maintained in the thread structure.
 */

#if CORTEX_USE_FPU
	#warning "GOS Threads: You have specified GFX_CPU=GFX_CPU_CORTX_M? with no hardware floating point support but CORTEX_USE_FPU is TRUE. Try using GFX_CPU_GFX_CPU_CORTEX_M?_FP instead"
#endif

#if GFX_COMPILER == GFX_COMPILER_GCC || GFX_COMPILER == GFX_COMPILER_CYGWIN || GFX_COMPILER == GFX_COMPILER_MINGW32 || GFX_COMPILER == GFX_COMPILER_MINGW64
	#define GFX_THREADS_DONE
	#define _gfxThreadsInit()

	static __attribute__((pcs("aapcs"),naked)) void _gfxTaskSwitch(thread *oldt, thread *newt) {
		__asm__ volatile (	"push    {r4, r5, r6, r7, lr}                   \n\t"
							"mov     r4, r8                                 \n\t"
							"mov     r5, r9                                 \n\t"
							"mov     r6, r10                                \n\t"
							"mov     r7, r11                                \n\t"
							"push    {r4, r5, r6, r7}						\n\t"
							"mov     r4, sp                                 \n\t"
							"str     r4, %[oldtcxt]							\n\t"
							"ldr     r4, %[newtcxt]							\n\t"
							"mov     sp, r4                                 \n\t"
							"pop     {r4, r5, r6, r7}                   	\n\t"
							"mov     r8, r4                                 \n\t"
							"mov     r9, r5                                 \n\t"
							"mov     r10, r6                                \n\t"
							"mov     r11, r7                                \n\t"
							"pop     {r4, r5, r6, r7, pc}					\n\t"
							: [newtcxt] "=m" (newt->cxt)
							: [oldtcxt] "m" (oldt->cxt)
							: "memory");
	}

	static __attribute__((pcs("aapcs"),naked)) void _gfxStartThread(thread *oldt, thread *newt) {
		newt->cxt = (void *)(((unsigned)newt + newt->size) & ~7);
		__asm__ volatile (	"push    {r4, r5, r6, r7, lr}                   \n\t"
							"mov     r4, r8                                 \n\t"
							"mov     r5, r9                                 \n\t"
							"mov     r6, r10                                \n\t"
							"mov     r7, r11                                \n\t"
							"push    {r4, r5, r6, r7}						\n\t"
							"mov     r4, sp                                 \n\t"
							"str     r4, %[oldtcxt]							\n\t"
							"ldr     r4, %[newtcxt]							\n\t"
							"mov     sp, r4                                 \n\t"
							: [newtcxt] "=m" (newt->cxt)
							: [oldtcxt] "m" (oldt->cxt)
							: "memory");

		// Run the users function
		gfxThreadExit(_gfxCurrentThread->fn(_gfxCurrentThread->param));
	}

#elif GFX_COMPILER == GFX_COMPILER_KEIL || GFX_COMPILER == GFX_COMPILER_ARMCC
	#define GFX_THREADS_DONE
	#define _gfxThreadsInit()

	static __asm void _gfxTaskSwitch(thread *oldt, thread *newt) {
		PRESERVE8

		// Save the old context
		push	{r4, r5, r6, r7, lr}
		mov     r4, r8
		mov     r5, r9
		mov     r6, r10
		mov     r7, r11
		push    {r4, r5, r6, r7}
		mov		r4, sp
		str		r4, [r0,#__cpp(offsetof(thread,cxt))]		// oldt->cxt
		
		// Load the new context
		ldr		r4, [r1,#__cpp(offsetof(thread,cxt))]		// newt->cxt
		mov		sp, r4
		pop     {r4, r5, r6, r7}
		mov     r8, r4
		mov     r9, r5
		mov     r10, r6
		mov     r11, r7
		pop     {r4, r5, r6, r7, pc}
	}

	static __asm void _gfxStartThread(thread *oldt, thread *newt) {
		PRESERVE8

		// Calculate where to generate the new context
		//		newt->cxt = (void *)(((unsigned)newt + newt->size) & ~7);
		ldr      r2,[r1,#__cpp(offsetof(thread,size))]
		add      r2,r2,r1
		and      r2, r2, #0xFFFFFFF8
		str      r2,[r1,#__cpp(offsetof(thread,cxt))]
		
		// Save the old context
		push	{r4, r5, r6, r7, lr}
		mov     r4, r8
		mov     r5, r9
		mov     r6, r10
		mov     r7, r11
		push    {r4, r5, r6, r7}
		mov		r4, sp
		str		r4, [r0,#__cpp(offsetof(thread,cxt))]	// oldt->cxt
		
		// Load the new (imcomplete) context
		ldr		r4, [r1,#__cpp(offsetof(thread,cxt))]	// newt->cxt
		mov		sp, r4
		
		// Run the users function - we save some code because gfxThreadExit() never returns
		//		gfxThreadExit(_gfxCurrentThread->fn(_gfxCurrentThread->param));
		ldr      r2,=__cpp(&_gfxCurrentThread)
		ldr      r2,[r2,#0]
		ldr      r0,[r2,#__cpp(offsetof(thread,param))]
		ldr      r1,[r2,#__cpp(offsetof(thread,fn))]
		blx      r1
		mov      r4,r0
		bl       __cpp(gfxThreadExit)
		
		ALIGN
	}

#else
	#warning "GOS: Threads: You have specified a specific CPU but your compiler is not supported. Defaulting to CLIB switching"
#endif
