/**
 * @file    sparc/include/asm/switch_to.h
 *
 * @copyright GPLv2
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 *
 * When implementing the actual task switching segment, I came up with
 * essentially the same thing that David S. Miller did in the SPARC port
 * switch_to() macro of the Linux kernel, just less tuned, so I adapted his
 * code.
 * Hence, I decided to just follow a similar scheme to Linux for the thread
 * switching, which may make it easier to port to a new architecure in the
 * future, as it'll be possible to adapt the according macros from the Linux
 * source tree. No need to reinvent the wheel..
 *
 *
 * TODO: FPU (lazy) switching
 * TODO: CPU id (for SMP)
 */


#include <kernel/kthread.h>
#include <asm/ttable.h>

#ifndef _ARCH_SPARC_ASM_SWITCH_TO_H_
#define _ARCH_SPARC_ASM_SWITCH_TO_H_



#define prepare_arch_switch(next) do {					\
__asm__ __volatile__(							\
"save %sp, -0x40, %sp; save %sp, -0x40, %sp; save %sp, -0x40, %sp\n\t"	\
"save %sp, -0x40, %sp; save %sp, -0x40, %sp; save %sp, -0x40, %sp\n\t"	\
"save %sp, -0x40, %sp\n\t"						\
"restore; restore; restore; restore; restore; restore; restore");	\
} while(0);



/* NOTE: we don't actually require the PSR_ET toggle, but if we have
 * unaligned accesses (or access traps), it is a really good idea, or
 * we'll die...
 */

/* NOTE: this assumes we have a mixed kernel/user mapping in the MMU (if
 * we are using it), otherwise we might would not be able to load the
 * thread's data. Oh, and we'll have to do a switch user->kernel->new
 * user OR we'll run into the same issue with different user contexts */

/* curptr is %g6! */


/* so, here's what's happening
 *
 *
 * 1+2: store a new program counter (~ return address) just after the actual
 *	switch block, so the old thread will hop over the actual switching
 *	section when it is re-scheduled.
 *
 * NOTE: in the SPARC ABI the return address to the caller in %i7 is actually
 *	(return address - 0x8), and the %o[] regs become the %i[] regs on a
 *	save instruction, so we actually have to store the reference address
 *	to the jump accordingly
 *
 * 3:	store the current thread's %psr to %g4
 *
 * 4:	double-store the stack pointer (%o6) and the "skip" PC in %o7
 *	note that this requires double-word alignment of struct thread_info
 *	members KSP an KPC
 *
 * 5:	store the current thread's %wim to %g5
 *
 * 6-7:	toggle the enable traps bit in the %psr (should be off at this point!)
 *	and wait 3 cycles for the bits to settle
 *
 * 8:	double-store store the PSR in %g4 and the WIM in %g5
 *	note that this requires double-word alignment of struct thread_infio
 *	members KPSR an KWIM
 *
 * 9:	double-load KPSR + KWIM into %g4, %g5 from new thread info
 *
 * NOTE: A double load takes 2 cycles, +1 extra if the subsequent instruction
 *	 depends on the result of the load, that's why we don't set %g6 first
 *	 and use it to load  steps 10+11 form there
 *
 * 10:	set the new thread info to "curptr" (%g6) of this CPU
 *
 * 11:	set the new thread info to the global "current" set of this CPU
 *
 * 12:	set the new thread's PSR and toggle the ET bit (should be off)
 *
 * 13:	wait for the bits to settle, so the CPU puts us into the proper window
 *	before we can continue
 *
 * 14:  double-load KSP and KPC to %sp (%o6) and the "skip" PC in %o7
 *
 * 15:	set the new thread's WIM
 *
 * 16:	restore %l0 and %l1 from the memory stack, rtrap.S expects these to be
 *	l0 == t_psr, l1 == t_pc
 *
 * 17: restore the frame pointer %fp (%i6) and the return address in %i7
 *
 * 18: restore the new thread's PSR
 *
 * NOTE: we don't have to wait there, as long as we don't return immediately
 *	following the macro
 *
 * 19: jump to the actual address of the label
 *
 *
 *
 * The corresponding (approximate) c code:
 *
 *	register struct sparc_stackf *sp asm("sp");
 *	register unsigned long calladdr asm("o7");
 *	register struct thread_info *th asm("g6");
 *	register unsigned long t_psr asm("l0");
 *	register unsigned long t_pc asm("l1");
 *	register unsigned long fp asm("fp");
 *	register unsigned long ret asm("i7");
 *
 *
 *	th->kpc  = (unsigned long) &&here - 0x8;
 *	th->kpsr = get_psr();
 *	th->ksp  = (unsigned long) sp;
 *	th->kwim = get_wim();
 *
 *	put_psr(th->kpsr^0x20);
 *
 *	th = &next->thread_info;
 *	current_set[0] = th;
 *
 *	put_psr(th->kpsr^0x20);
 *	put_wim(th->kwim);
 *
 *	calladdr = th->kpc;
 *	sp = (struct sparc_stackf *) th->ksp;
 *
 *	t_psr = sp->locals[0];
 *	t_pc = sp->locals[1];
 *
 *	fp = (unsigned long) sp->fp;
 *	ret = sp->callers_pc;
 *
 *	put_psr(th->kpsr);
 *
 *	__asm__ __volatile__(
 *		"jmpl	%%o7 + 0x8, %%g0\n\t"
 *		"nop\n\t"
 *		::: "%o7", "memory");
 *	here:
 *		(void) 0;
 */


#define switch_to(next)	do {					\
	__asm__ __volatile__(					\
	"sethi	%%hi(here - 0x8), %%o7\n\t"			\
	"or	%%o7, %%lo(here - 0x8), %%o7\n\t"		\
	"rd	%%psr, %%g4\n\t"				\
	"std	%%sp, [%%g6 + %2]\n\t"				\
	"rd	%%wim, %%g5\n\t"				\
	"wr	%%g4, 0x20, %%psr\n\t"				\
	"nop; nop; nop\n\t"					\
	"std	%%g4, [%%g6 + %4]\n\t"				\
	"ldd	[%1 + %4], %%g4\n\t"				\
	"mov	%1, %%g6\n\t"					\
	"st	%1, [%0]\n\t"					\
	"wr	%%g4, 0x20, %%psr\n\t"				\
	"nop; nop; nop\n\t"					\
	"ldd	[%%g6 + %2], %%sp\n\t"				\
	"wr	%%g5, 0x0, %%wim\n\t"				\
	"ldd	[%%sp + 0x00], %%l0\n\t"			\
	"ldd	[%%sp + 0x38], %%i6\n\t"			\
	"wr	%%g4, 0x0, %%psr\n\t"				\
	"jmpl %%o7 + 0x8, %%g0\n\t"				\
	" nop\n\t"						\
	"here:\n\t"						\
	:							\
	: "r" (&(current_set[smp_cpu_id()])),			\
	  "r" (&(next->thread_info)),				\
	  "i" (TI_KSP),						\
	  "i" (TI_KPC),						\
	  "i" (TI_KPSR)						\
	:       "g1", "g2", "g3", "g4", "g5",       "g7",	\
	  "l0", "l1",       "l3", "l4", "l5", "l6", "l7",	\
	  "i0", "i1", "i2", "i3", "i4", "i5",			\
	  "o0", "o1", "o2", "o3",                   "o7");	\
} while(0);




#endif /* _ARCH_SPARC_ASM_SWITCH_TO_H_ */