/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * This file is through cpp before being used as
 * an inline.  It contains support routines used
 * only by DR for the copy-rename sequence.
 */

#if defined(lint)
#include <sys/types.h>
#else
#include "assym.h"
#include "drmach_offsets.h"
#endif /* lint */

#include <sys/asm_linkage.h>
#include <sys/param.h>
#include <sys/privregs.h>
#include <sys/spitregs.h>
#include <sys/mmu.h>
#include <sys/machthread.h>
#include <sys/pte.h>
#include <sys/stack.h>
#include <sys/vis.h>
#include <sys/intreg.h>
#include <sys/cheetahregs.h>
#include <sys/drmach.h>
#include <sys/sbd_ioctl.h>

#if !defined(lint)

/*
 * turn off speculative mode to prevent unwanted memory access
 * when we are in the FMEM loops
 */

#define	FJSV_SPECULATIVE_OFF(reg, tmp1, tmp2)				\
	rdpr	%pstate, reg						;\
	andn	reg, PSTATE_IE, tmp1					;\
	wrpr	%g0, tmp1, %pstate					;\
	ldxa	[%g0]ASI_MCNTL, tmp1					;\
	set	1, tmp2							;\
	sllx	tmp2, MCNTL_SPECULATIVE_SHIFT, tmp2						;\
	or	tmp1, tmp2, tmp1					;\
	stxa	tmp1, [%g0]ASI_MCNTL					;\
	membar #Sync
#endif


#if defined(lint)
/*ARGSUSED*/
void
drmach_fmem_loop_script(caddr_t critical, int size, caddr_t stat)
{ return; }
#else /* lint */
	.align  8
	ENTRY_NP(drmach_fmem_loop_script)
	/* turn off speculative mode */
	FJSV_SPECULATIVE_OFF(%o5, %o3, %o4);

	/* read the critical region to get everything in the cache */
	mov	%o0, %o3
0:
	ldx	[%o3], %o4
	sub	%o1, 8, %o1
	brnz	%o1, 0b
	 add	%o3, 8, %o3

	/* clear L2_CTRL_UGE_TRAP error bit */
	mov	ASI_L2_CTRL_RW_ADDR, %o1
	ldxa	[%o1]ASI_L2_CTRL, %o3
	sethi	%hi(ASI_L2_CTRL_UGE_TRAP), %o4
	btst	%o3, %o4
	bz,pn	%xcc, 1f
	 nop
	stxa	%o4, [%o1]ASI_L2_CTRL

	/* now tell the master CPU that we are ready */
1:
	set	FMEM_LOOP_FMEM_READY, %o3
	stb	%o3, [%o2]
	membar #Sync
	ba	 5f
	 nop

	/*
	 * note that we branch to 5f, which branches right back to 2 here.
	 * The trick is that when that branch instruction has already been
	 * patched to a branch to itself - an infinite loop.
	 * The master thread will patch it back to "ba 2b" when it
	 * completes.
	 */

	/* Once we are back, we first check if there has been any
	 * L2_CTRL_UGE_TRAP errors, if so we have to fail the
	 * operation.  This will cause a panic because the system
	 * is already in inconsistent state.
	 */
2:
	mov	ASI_L2_CTRL_RW_ADDR, %o3
	ldxa	[%o3]ASI_L2_CTRL, %o3
	sethi	%hi(ASI_L2_CTRL_UGE_TRAP), %o4
	btst	%o3, %o4
	bz,pn	%xcc, 3f
	 mov	%g0, %o4
	set	EOPL_FMEM_HW_ERROR, %o4

	/* set error code and stat code */
3:
	set	FMEM_LOOP_DONE, %o3
	stb	%o3, [%o2]

	/* turn on speculative mode again */
	ldxa	[%g0]ASI_MCNTL, %o0
	set	1, %o1
	sllx	%o1, MCNTL_SPECULATIVE_SHIFT, %o1
	andn	%o0, %o1, %o0
	ba	4f
	 nop
.align 32
4:
	stxa	%o0, [%g0]ASI_MCNTL
	membar	#Sync
	wrpr	%g0, %o5, %pstate
	retl
	 mov	%o4, %o0
.align 8
5:
	ALTENTRY(drmach_fmem_loop_script_rtn)
	/*
	 * busy wait will affect sibling strands so
	 * we put sleep instruction in the delay slot
	 */
	ba	2b
.word	 0x81b01060
	SET_SIZE(drmach_fmem_loop_script)
#endif /* lint */

#if defined(lint)
/*ARGSUSED*/
void
drmach_flush_icache(void)
{ return; }
#else /* lint */
	.align  8
	ENTRY_NP(drmach_flush_icache)
	stxa	%g0, [%g0]ASI_ALL_FLUSH_L1I
	membar	#Sync
	retl
	 nop
	SET_SIZE(drmach_flush_icache)
#endif

#if defined(lint)
/*ARGSUSED*/
int
drmach_fmem_exec_script(caddr_t critical, int size)
{ return (0); }
#else /* lint */
.align 32
	ENTRY_NP(drmach_fmem_exec_script)
	/* turn off speculative mode */
	FJSV_SPECULATIVE_OFF(%o5, %o3, %o4);
	/* save locals to save area */
	add	%o0, SAVE_LOCAL, %o2
	stx	%l0, [%o2+8*0]
	stx	%l1, [%o2+8*1]
	stx	%l2, [%o2+8*2]
	stx	%l3, [%o2+8*3]
	stx	%l4, [%o2+8*4]
	stx	%l5, [%o2+8*5]
	stx	%l6, [%o2+8*6]
	stx	%l7, [%o2+8*7]
	mov	%o5, %l6
	/* l7 is set only when FMEM cmd is issued to SCF */
	mov	%g0, %l7

	/* read the critical region to put everything in the cache */
	mov	%o0, %o2
0:
	ldx	[%o2], %o4
	sub	%o1, 8, %o1
	brnz	%o1, 0b
	 add	%o2, 8, %o2
	ba	4f
	 nop

	/* we branch to 4f but eventually we branch back here to finish up */
1:
	mov	%l6, %o5
	/*
	 * save some registers for debugging
	 * l0 - SCF_REG_BASE
	 * l1 - SCF_TD
	 * l2 - SCF_TD + 8
	 * l5 - DELAY
	 */
	add	%o0, SAVE_LOG, %o1
	stx	%l0, [%o1+8*0]
	stx	%l1, [%o1+8*1]
	stx	%l2, [%o1+8*2]
	stx	%l5, [%o1+8*3]

	add	%o0, FMEM_ISSUED, %o1
	st	%l7, [%o1]

	/* Check for L2_CTRL_UGE_TRAP error */
	mov	ASI_L2_CTRL_RW_ADDR, %l0
	ldxa	[%l0]ASI_L2_CTRL, %l1
	sethi	%hi(ASI_L2_CTRL_UGE_TRAP), %l2
	btst	%l1, %l2
	bz,pn	%xcc, 2f
	 nop
	set	EOPL_FMEM_HW_ERROR, %o4
2:
	/* restore all locals */
	add	%o0, SAVE_LOCAL, %o1
	ldx	[%o1+8*0], %l0
	ldx	[%o1+8*1], %l1
	ldx	[%o1+8*2], %l2
	ldx	[%o1+8*3], %l3
	ldx	[%o1+8*4], %l4
	ldx	[%o1+8*5], %l5
	ldx	[%o1+8*6], %l6
	ldx	[%o1+8*7], %l7

	/* turn on speculative mode */
	ldxa	[%g0]ASI_MCNTL, %o1
	set	1, %o2
	sllx	%o2, MCNTL_SPECULATIVE_SHIFT, %o2
	andn	%o1, %o2, %o1
	ba	3f
	 nop
.align 32
3:
	stxa	%o1, [%g0]ASI_MCNTL
	membar	#Sync
	/* return error code here */
	mov	%o4, %o0
	retl
	 wrpr	%g0, %o5, %pstate

	/* clear L2_CTRL_UGE_TRAP error bit */
4:
	mov	ASI_L2_CTRL_RW_ADDR, %l0
	ldxa	[%l0]ASI_L2_CTRL, %l1
	sethi	%hi(ASI_L2_CTRL_UGE_TRAP), %l2
	btst	%l1, %l2
	bz,pn	%xcc, 5f
	 nop
	stxa	%l2, [%l0]ASI_L2_CTRL
5:
	/* set up the register locations and parameters */
	ldx	[%o0 + SCF_REG_BASE], %l0
	ldx	[%o0 + SCF_TD], %l1
	ldx	[%o0 + SCF_TD+8], %l2
	ldx	[%o0 + DELAY], %l5

	/* check if SCF is busy */
	add	%l0, SCF_COMMAND, %o1
	lduha	[%o1]ASI_IO, %o2
	sethi	%hi(SCF_CMD_BUSY), %o3
	btst	%o2, %o3
	be	%xcc, 6f
	 nop
	set	EOPL_FMEM_SCF_BUSY, %o4
	ba	1b
	 nop

	/* clear STATUS bit */
6:
	add	%l0, SCF_STATUS, %o1
	lduha	[%o1]ASI_IO, %o2
	sethi	%hi(SCF_STATUS_READY), %o3
	btst	%o2, %o3
	be	%xcc, 7f
	 nop
	stha	%o3, [%o1]ASI_IO

	/* clear CMD_COMPLETE bit */
7:
	mov	SCF_STATUS_CMD_COMPLETE, %o3
	btst	%o2, %o3
	be,a	%xcc, 8f
	 nop
	stha	%o3, [%o1]ASI_IO
8:
	add	%l0, (SCF_TDATA+0xe), %o1
	mov	%l2, %o4
	mov	SCF_RETRY_CNT, %o5

	sethi	%hi(0xffff), %l2
	or	%l2, %lo(0xffff), %l2

	and	%o4, %l2, %o3

	/*
	 * o1 points to SCFBASE.SCF_TDATA[0xe]
	 * l0 points to SCFBASE
	 * crticial->SCF_TD[0] = source board #
	 * crticial->SCF_TD[1] = target board #
	 * l1 = critical->SCF_TD[0 - 7]
	 * l2 = 0xffff
	 * o4 = critical->SCF_TD[8 - 15]
	 * o3 = (*o4) & 0xffff

	/*
	 * Because there is no parity protection on the ebus
	 * we read the data back after the write to verify
	 * we write 2 bytes at a time.
	 * If the data read is not the same as data written
	 * we retry up to a limit of SCF_RETRY_CNT
	 */
9:
	stha	%o3, [%o1]ASI_IO
	lduha	[%o1]ASI_IO, %o2
	sub	%o5, 1, %o5
	brnz	%o5, 7f
	 nop
	set	EOPL_FMEM_RETRY_OUT, %o4
	ba	1b
	 nop
7:
	cmp	%o2, %o3
	bne,a	9b
	 nop

	sub	%o1, %l0, %o2
	cmp	%o2, (SCF_TDATA+0x8)
	bne	%xcc, 2f
	 srlx	%o4, 16, %o4
	mov	%l1, %o4

	/* if we have reach TDATA+8, we switch to l1 */
	/* XXX: Why we need 2 loops??? */
2:
	sub	%o1, 2, %o1
	mov	SCF_RETRY_CNT, %o5
	and	%o4, %l2, %o3

	sub	%o1, %l0, %o2
	cmp	%o2, (SCF_TDATA)
	bge,a	9b
	 nop

	/* if we reach TDATA, we are done */

	/* read from SCF back to our buffer for debugging */
	add	%l0, (SCF_TDATA), %o1
	ldxa	[%o1]ASI_IO, %o2
	stx	%o2, [%o0+SCF_TD]

	add	%l0, (SCF_TDATA+8), %o1
	ldxa	[%o1]ASI_IO, %o2
	stx	%o2, [%o0+SCF_TD+8]

	/* The following code conforms to the FMEM
	   sequence (4) as described in the Columbus2
	   logical spec section 4.6
	*/

	/* read from SCF SB INFO register */
	sethi	%hi(SCF_SB_INFO_OFFSET), %o2
	or	%o2, %lo(SCF_SB_INFO_OFFSET), %o2
	add	%l0, %o2, %o1
	lduba	[%o1]ASI_IO, %o2

	/* If BUSY bit is set, abort */
	or	%g0, (SCF_SB_INFO_BUSY), %o1
	btst	%o1, %o2
	set	EOPL_FMEM_SCF_BUSY, %o4
	bne	1b
	 nop

	rd	STICK, %l1
	add	%l5, %l1, %l5

	/* Now tell SCF to do it */
	add	%l0, SCF_COMMAND, %o1

	/* 0x10A6 is the magic command */
	sethi	%hi(0x10A6), %o2
	or	%o2, %lo(0x10A6), %o2
	stha	%o2, [%o1]ASI_IO

	mov	1, %l7			! FMEM is issued

	add	%l0, SCF_STATUS, %o1
	sethi	%hi(SCF_STATUS_READY), %o2
	mov	SCF_STATUS_CMD_COMPLETE, %o3

	/* read STATUS_READY bit and clear it only if it is set */
	/* XXX: this STATUS_READY checking seems meaningless */
3:
	lduha	[%o1]ASI_IO, %o4
	btst	%o2, %o4
	be	%xcc, 4f		! STATUS_READY is not set
	 nop
	stha	%o2, [%o1]ASI_IO	! Clear if the bit is set

	/* check CMD_COMPLETE bit and clear */
4:
	btst	%o3, %o4
	be	%xcc, 5f		! CMD_COMPLETE is not set
	 nop
	stha	%o3, [%o1]ASI_IO	! Now we are done and clear it
	ba	%xcc, 6f
	 mov	ESBD_NOERROR, %o4

	/* timeout delay checking */
5:
	rd	STICK, %l2
	cmp	%l5, %l2
	bge	%xcc, 3b
	 nop
	set	EOPL_FMEM_TIMEOUT, %o4

	/* we are done or timed out */
6:
	ba,a	1b
	 nop
	SET_SIZE(drmach_fmem_exec_script)
#endif /* lint */

#if defined(lint)
/*ARGSUSED*/
void
drmach_fmem_exec_script_end(caddr_t critical, int size)
{ return; }
#else /* lint */
	ENTRY_NP(drmach_fmem_exec_script_end)
	nop
	SET_SIZE(drmach_fmem_exec_script_end)
#endif /* lint */

#if defined(lint)
uint64_t
patch_inst(uint64_t *x, uint64_t y)
{
	*x = y;
	return (0);
}

#else   /* lint */

	ENTRY_NP(patch_inst)
	ldx	[%o0], %o2
	casx	[%o0], %o2, %o1
	flush	%o0
	membar #Sync
	ldx	[%o0], %o2
	retl
	 mov	%o2, %o0
	SET_SIZE(patch_inst)

#endif /* lint */

#if defined(lint)
void
drmach_sys_trap()
{
}
#else   /* lint */
	ENTRY_NP(drmach_sys_trap)
	mov	-1, %g4
	set	sys_trap, %g5
	jmp	%g5
	 nop
	SET_SIZE(drmach_sys_trap)
#endif /* lint */

#if defined(lint)
uint64_t
drmach_get_stick()
{
	return (0);
}
#else   /* lint */
	ENTRY_NP(drmach_get_stick)
	retl
	rd	STICK, %o0
	SET_SIZE(drmach_get_stick)
#endif /* lint */

#if defined(lint)
/*ARGSUSED*/
void
drmach_flush(void)
{}

#else /* lint */
	ENTRY_NP(drmach_flush)
	mov	%o0, %o2
0:
	flush	%o2
	sub	%o1, 8, %o1
	brnz	%o1, 0b
	 add	%o2, 8, %o2
	retl
	 nop
	SET_SIZE(drmach_flush)
#endif /* lint */
