/* Overlay manager for SPU.

   Copyright 2006, 2007 Free Software Foundation, Inc.

   This file is part of the GNU Binutils.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that 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.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

/**
 * MFC DMA defn's.
 */
#define MFC_GET_CMD		0x40
#define MFC_MAX_DMA_SIZE	0x4000
#define MFC_TAG_UPDATE_ALL	2
#define MFC_TAG_ID		0


/**
 * Temporary register allocations.
 * These are saved/restored here.
 */
#define tab		$75
#define cgbits		$75
#define add64		$75
#define ealo		$75
#define newmask		$75
#define tagstat		$75
#define bchn		$75
#define rv1		$75

#define off		$76
#define off64		$76
#define maxsize		$76
#define oldmask		$76
#define sz		$76
#define lnkr		$76
#define rv2		$76

#define cur		$77
#define cmp		$77
#define buf		$77
#define genwi		$77
#define tagid		$77
#define cmd		$77
#define rv3		$77

#define cgshuf		$78

#define vma		$6

#define map		$7
#define osize		$7
#define cmp2		$7

#define ea64		$8
#define retval		$8

#ifdef OVLY_IRQ_SAVE
#define irqtmp		$8
#define irq_stat	$9
#endif

	.extern		_ovly_table
	.extern		_ovly_buf_table

	.text
	.align 		4
	.type		__rv_pattern, @object
	.size		__rv_pattern, 16
__rv_pattern:
	.word		0x00010203, 0x1c1d1e1f, 0x00010203, 0x10111213
	.type		__cg_pattern, @object
	.size		__cg_pattern, 16
__cg_pattern:
	.word		0x04050607, 0x80808080, 0x80808080, 0x80808080

/**
 * __ovly_return - stub for returning from overlay functions.
 *
 * inputs:
 *	$lr	link register
 *
 * outputs:
 *	$78	old partition number, to be reloaded
 *	$79	return address in old partion number
 */
	.global		__ovly_return
	.type		__ovly_return, @function

	.word		0
__ovly_return:
	shlqbyi		$78, $lr, 4
	shlqbyi		$79, $lr, 8
	biz		$78, $79
	.size		__ovly_return, . - __ovly_return

/**
 * __ovly_load - copy an overlay partion to local store.
 *
 * inputs:
 *	$78	partition number to be loaded.
 *	$79	branch target in new partition.
 *	$lr	link register, containing return addr.
 *
 * outputs:
 *	$lr	new link register, returning through __ovly_return.
 *
 * Copy a new overlay partition into local store, or return 
 * immediately if the partition is already resident.
 */
	.global		__ovly_load
	.type		__ovly_load, @function

__ovly_load:
/* Save temporary registers to stack. */
	stqd		$6, -16($sp)
	stqd		$7, -32($sp)
	stqd		$8, -48($sp)

#ifdef OVLY_IRQ_SAVE
/* Save irq state, then disable interrupts. */ 
	stqd		$9, -64($sp)
	ila		irqtmp, __ovly_irq_save
	rdch		irq_stat, $SPU_RdMachStat
	bid		irqtmp
__ovly_irq_save:
#endif

/* Set branch hint to overlay target. */
	hbr		__ovly_load_ret, $79

/* Get caller's overlay index by back chaining through stack frames.
 * Loop until end of stack (back chain all-zeros) or
 * encountered a link register we set here. */
	lqd		bchn, 0($sp)
	ila		retval, __ovly_return

__ovly_backchain_loop:
	lqd		lnkr, 16(bchn)
	lqd		bchn, 0(bchn)
	ceq		cmp, lnkr, retval
	ceqi		cmp2, bchn, 0
	or		cmp, cmp, cmp2	
	brz		cmp, __ovly_backchain_loop

/* If we reached the zero back-chain, then lnkr is bogus.  Clear the
 * part of lnkr that we use later (slot 3). */
	rotqbyi		cmp2, cmp2, 4
	andc		lnkr, lnkr, cmp2

/* Set lr = {__ovly_return, prev ovl ndx, caller return adr, callee ovl ndx}. */
	lqd		rv1, (__rv_pattern-__ovly_return+4)(retval)
	shufb		rv2, retval, lnkr, rv1
	shufb		rv3, $lr, $78, rv1
	fsmbi		rv1, 0xff
	selb		rv2, rv2, rv3, rv1
/* If we have a tail call from one overlay function to another overlay,
   then lr is already set up.  Don't change it.  */
	ceq		rv1, $lr, retval
	fsmb		rv1, rv1
	selb		$lr, rv2, $lr, rv1

/* Branch to $79 if non-overlay */
	brz		$78, __ovly_load_restore

/* Load values from _ovly_table[$78].
 *	extern struct {
 *		u32 vma;
 *		u32 size;
 *		u32 file_offset;
 *		u32 buf;
 *	} _ovly_table[];
 */
	shli		off, $78, 4
	ila		tab, _ovly_table - 16
	lqx		vma, tab, off
	rotqbyi		buf, vma, 12

/* Load values from _ovly_buf_table[buf].
 *	extern struct {
 *		u32 mapped;
 *	} _ovly_buf_table[];
 */
	ila		tab, _ovly_buf_table
	ai		off, buf, -1
	shli		off, off, 2
	lqx		map, tab, off
	rotqby		cur, map, off

/* Branch to $79 now if overlay is already mapped.  */
	ceq		cmp, $78, cur
	brnz		cmp, __ovly_load_restore

/* Marker for profiling code.  If we get here, we are about to load
 * a new overlay.
 */
	.global		__ovly_load_event
	.type		__ovly_load_event, @function
__ovly_load_event:

/* Set _ovly_buf_table[buf].mapped = $78. */
	cwx		genwi, tab, off
	shufb		map, $78, map, genwi
	stqx		map, tab, off

/* A new partition needs to be loaded. Prepare for DMA loop. 
 * _EAR_ is the 64b base EA, filled in at run time by the
 * loader, and indicating the value for SPU executable image start.
 */
	lqd		cgshuf, (__cg_pattern-__ovly_return+4)(retval)
	rotqbyi		osize, vma, 4
	rotqbyi		sz, vma, 8
	lqa		ea64, _EAR_

__ovly_xfer_loop:
/* 64b add to compute next ea64. */
	rotqmbyi	off64, sz, -4
	cg		cgbits, ea64, off64
	shufb		add64, cgbits, cgbits, cgshuf
	addx		add64, ea64, off64
	ori		ea64, add64, 0

/* Setup DMA parameters, then issue DMA request. */
	rotqbyi		ealo, add64, 4
	ila		maxsize, MFC_MAX_DMA_SIZE
	cgt		cmp, osize, maxsize
	selb		sz, osize, maxsize, cmp
	ila		tagid, MFC_TAG_ID
	wrch		$MFC_LSA, vma
	wrch		$MFC_EAH, ea64
	wrch		$MFC_EAL, ealo
	wrch		$MFC_Size, sz
	wrch		$MFC_TagId, tagid
	ila		cmd, MFC_GET_CMD
	wrch		$MFC_Cmd, cmd

/* Increment vma, decrement size, branch back as needed. */
	a		vma, vma, sz
	sf		osize, sz, osize
	brnz		osize, __ovly_xfer_loop

/* Save app's tagmask, wait for DMA complete, restore mask. */
	rdch		oldmask, $MFC_RdTagMask
#if MFC_TAG_ID < 16
	ilh		newmask, 1 << MFC_TAG_ID
#else
	ilhu		newmask, 1 << (MFC_TAG_ID - 16)
#endif
	wrch		$MFC_WrTagMask, newmask
	ila		tagstat, MFC_TAG_UPDATE_ALL
	wrch		$MFC_WrTagUpdate, tagstat
	rdch		tagstat, $MFC_RdTagStat
	sync
	wrch		$MFC_WrTagMask, oldmask

	.global		_ovly_debug_event
	.type		_ovly_debug_event, @function
_ovly_debug_event:
/* GDB inserts debugger trap here.  */
	nop

__ovly_load_restore:
#ifdef OVLY_IRQ_SAVE
/* Conditionally re-enable interrupts. */
	andi		irq_stat, irq_stat, 1
	ila		irqtmp, __ovly_irq_restore
	binze		irq_stat, irqtmp
__ovly_irq_restore:
	lqd		$9, -64($sp)
#endif

/* Restore saved registers. */
	lqd		$8, -48($sp)
	lqd		$7, -32($sp)
	lqd		$6, -16($sp)

__ovly_load_ret:
/* Branch to target address. */
	bi		$79

	.size		__ovly_load, . - __ovly_load
