/*
 * 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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * This file contains the low-level DMV interrupt
 * handler for IDN cross-domain interrupts.
 */

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

#include <sys/asm_linkage.h>
#include <sys/machasi.h>
#include <sys/privregs.h>
#include <sys/intreg.h>
#include <sys/machthread.h>

#include <sys/idn.h>

#if !defined(lint)
#include "idn_offsets.h"
#endif /* !lint */

#define	IDN_MONDO

/*
 * The IDN_DMV_CPU_SHIFT is based on the sizeof (idn_dmv_cpu_t)
 * which must be a power of 2 to optimize calculating our
 * entry into idn_dmv_cpu[].
 */
#define	IDN_DMV_CPU_SHIFT	4

/*
 *--------------------------------------------------------
 */
#if defined(lint)

/*
 * Would be nice to use init_mondo, but unforunately
 * it assumes the first arg is 32-bits.
 */
/*ARGSUSED*/
void
idnxf_init_mondo(uint64_t arg0, uint64_t arg1, uint64_t arg2)
{}

#else /* lint */

	.global _idn_dispatch_status_busy
_idn_dispatch_status_busy:
	.asciz	"ASI_INTR_DISPATCH_STATUS error: busy"
	.align	4

	ENTRY_NP(idnxf_init_mondo)
#ifdef DEBUG
	!
	! IDSR should not be busy at the moment - borrowed from init_mondo
	!
	ldxa	[%g0]ASI_INTR_DISPATCH_STATUS, %g1
	btst	IDSR_BUSY, %g1
	bz,pt	%xcc, 1f
	mov	ASI_INTR_DISPATCH, %asi
	sethi	%hi(_idn_dispatch_status_busy), %o0
	call	panic
	or	%o0, %lo(_idn_dispatch_status_busy), %o0
#endif /* DEBUG */

	mov	ASI_INTR_DISPATCH, %asi
1:
	stxa	%o0, [IDDR_0]%asi	! dmv_word0
	stxa	%o1, [IDDR_1]%asi	! dmv_word1
	stxa	%o2, [IDDR_2]%asi	! dmv_word2

	retl
	membar	#Sync

	SET_SIZE(idnxf_init_mondo)

#endif /* lint */
/*
 *--------------------------------------------------------
 */
#if defined(lint)

/*
 * Unfortunately, send_mondo is rather picky about getting
 * a result from the cpu it sends an interrupt to.  If it
 * doesn't get a result within a specific timeframe it
 * will panic!  For IDN that's not cool since a cpu hungup
 * in one could ultimately result in the demise of a cpu
 * in another domain.  Instead of getting our panties in
 * a bind, we simply bail out.
 */
/*ARGSUSED*/
int
idnxf_send_mondo(int upaid)
{ return (0); }

#else /* lint */

	.seg	".data"

	.global _idn_send_mondo_failure
_idn_send_mondo_failure:
	.word	0

	.seg	".text"
	ENTRY(idnxf_send_mondo)
	!
	! NOTE:
	!	This is stolen from send_mondo.  The changes
	!	are those ifdef'd with IDN_MONDO
	!
	! construct the interrupt dispatch command register in %g1
	! also, get the dispatch out as SOON as possible
	! (initial analysis puts the minimum dispatch time at around
	!  30-60 cycles.  hence, we try to get the dispatch out quickly
	!  and then start the rapid check loop).
	!
	rd	%tick, %o4			! baseline tick
	sll	%o0, IDCR_PID_SHIFT, %g1	! IDCR<18:14> = upa port id
	or	%g1, IDCR_OFFSET, %g1		! IDCR<13:0> = 0x70
	stxa	%g0, [%g1]ASI_INTR_DISPATCH	! interrupt vector dispatch
#if defined(SF_ERRATA_54)
	membar	#Sync				! store must occur before load
	mov	0x20, %g3			! UDBH Control Register Read
	ldxa	[%g3]ASI_SDB_INTR_R, %g0
#endif
	membar	#Sync
	clr	%o2				! clear NACK counter
	clr	%o3				! clear BUSY counter

	!
	! how long, in ticks, are we willing to wait completely
	!
	sethi	%hi(xc_tick_limit), %g2
	ldx	[%g2 + %lo(xc_tick_limit)], %g2
	add	%g2, %o4, %o5			! compute the limit value

	!
	! check the dispatch status
	!
.check_dispatch:
	ldxa	[%g0]ASI_INTR_DISPATCH_STATUS, %o1
	brz,pn	%o1, .dispatch_complete
	  rd	%tick, %g5

	!
	! see if we've gone beyond the limit
	! (can tick ever overflow?)
	!
.timeout_primed:
	sub	%o5, %g5, %g2			! limit - tick < 0 if timeout
	brgez,pt %g2, .check_busy
	  inc	%o3				! bump the BUSY counter

#ifdef IDN_MONDO
	!
	! Within the context of IDN we don't want
	! to panic just because we can't send_mondo.
	! Clear the dispatch register and increment
	! our count of failures.
	!
	stxa	%g0, [%g1]ASI_INTR_DISPATCH
	sethi	%hi(_idn_send_mondo_failure), %o0
	ld	[%o0 + %lo(_idn_send_mondo_failure)], %o1
	inc	%o1
	st	%o1, [%o0 + %lo(_idn_send_mondo_failure)]
	retl
	  mov	-1, %o0				! return (-1)
#else /* IDN_MONDO */
	!
	! time to die, see if we are already panicing
	! 
	mov	%o0, %o1			! save target
	sethi	%hi(_send_mondo_nack), %o0
	or	%o0, %lo(_send_mondo_nack), %o0
	sethi	%hi(panicstr), %g2
	ldn	[%g2 + %lo(panicstr)], %g2
	brnz	%g2, .dispatch_complete		! skip if already in panic
	  nop
	call	panic
	  nop
#endif /* IDN_MONDO */

.check_busy:
	btst	IDSR_BUSY, %o1			! was it BUSY?
	bnz,pt	%xcc, .check_dispatch
	  nop

	!
	! we weren't busy, we must have been NACK'd
	! wait a while and send again
	! (this might need jitter)
	!
	sethi	%hi(sys_clock_mhz), %g2
	lduw	[%g2 + %lo(sys_clock_mhz)], %g2
	rd	%tick, %g4
	add	%g2, %g4, %g2
.delay:
	cmp	%g2, %g4
	bgu,pt	%xcc, .delay
	rd	%tick, %g4

	stxa	%g0, [%g1]ASI_INTR_DISPATCH	! interrupt vector dispatch
#if defined(SF_ERRATA_54)
	membar	#Sync				! store must occur before load
	ldxa	[%g3]ASI_SDB_INTR_R, %g0
#endif
	membar	#Sync
	clr	%o3				! reset BUSY counter
	ba	.check_dispatch
	  inc	%o2				! bump the NACK counter

.dispatch_complete:
#ifndef IDN_MONDO
#ifdef SEND_MONDO_STATS
	!
	! Increment the appropriate entry in a send_mondo timeout array
	! x_entry[CPU][MSB]++;
	sub	%g5, %o4, %g5			! how long did we wait?
	clr	%o1				! o1 is now bit counter
1:	orcc	%g5, %g0, %g0			! any bits left?
	srlx	%g5, 1, %g5			! bits to the right
	bne,a,pt %xcc, 1b
	  add	%o1, 4, %o1			! pointer increment

	!
	! now compute the base of the x_early entry for our cpu
	!
	CPU_INDEX(%o0, %g5)
	sll	%o0, 8, %o0			! 64 * 4
	add	%o0, %o1, %o1			! %o0 = &[CPU][delay]

	!
	! and increment the appropriate value
	!
	sethi	%hi(x_early), %o0
	or	%o0, %lo(x_early), %o0
	ld	[%o0 + %o1], %g5
	inc	%g5
	st	%g5, [%o0 + %o1]
#endif	/* SEND_MONDO_STATS */
#endif /* !IDN_MONDO */
	retl
#ifdef IDN_MONDO
	  mov	%g0, %o0			! return (0)
#else /* IDN_MONDO */
	  nop
#endif /* IDN_MONDO */
	SET_SIZE(idnxf_send_mondo)

#endif /* lint */
/*
 *--------------------------------------------------------
 */
#if defined(lint)

/*ARGSUSED*/
void
idn_dmv_handler(void *arg)
{}

#else /* lint */

	ENTRY_NP(idn_dmv_handler)
	!
	! On entry:
	!	g1 = idn_dmv_data
	!	g2 = word 0
	!
	ldx	[%g1 + IDN_DMV_QBASE], %g4	! g4 = idn_dmv_qbase
	add	%g1, IDN_DMV_CPU, %g3		! g3 = &idn_dmv_cpu[0]

	CPU_INDEX(%g6, %g5)		! g6 = cpuid

	!
	! g5 = cur = idn_dmv_cpu[cpuid]
	!
	sll	%g6, IDN_DMV_CPU_SHIFT, %g6	! g6 = cpuid * 8
	add	%g3, IDN_DMV_CURRENT, %g3
	ld	[%g6 + %g3], %g5
	!
	! g5 = idn_dmv_cpu[cpuid].idn_dmv_current
	!      offset from idn_dmv_qbase
	!
	or	%g5, %g0, %g5		! get to 64-bits
	add	%g5, %g4, %g5		! g5 = idn_dmv_current
					!      actual address
	ldstub	[%g5 + IV_INUSE], %g7	! cur->iv_inuse = 0xff
	brz,pt	%g7, 1f			! did we get it?
	sub	%g3, IDN_DMV_CURRENT, %g4

	!
	! Queue is FULL.  Drop interrupt.
	!
	add	%g4, IDN_DMV_LOSTINTR, %g3
	ld	[%g6 + %g3], %g2
	!
	! g2 = idn_dmv_cpu[cpuid].idn_iv_lostintr++
	!
	inc	%g2
	set	dmv_finish_intr, %g4
	st	%g2, [%g3 + %g6]
	jmp	%g4
	mov	-1, %g1
	!
	! not reached
	!

1:
	add	%g4, IDN_DMV_ACTIVE, %g7
	!
	! Move current pointer to next one.
	! idn_dmv_current[cpuid] = cur->iv_next
	!
	ld	[%g5 + IV_NEXT], %g4
	st	%g4, [%g3 + %g6]

	!
	! Start filling in structure with data.
	!
	stx	%g2, [%g5 + IV_HEAD]

	mov	IRDR_1, %g2
	mov	IRDR_2, %g4
	ldxa	[%g2]ASI_INTR_RECEIVE, %g2	! g2 = xargs[0,1]
	ldxa	[%g4]ASI_INTR_RECEIVE, %g4	! g4 = xargs[2,3]

	stx	%g2, [%g5 + IV_XARGS0]
	stx	%g4, [%g5 + IV_XARGS2]

	membar	#StoreLoad|#StoreStore

	clrb	[%g5 + IV_READY]	! cur->iv_ready = 0 (unlocked)

	!
	! See if we're already active, i.e. have things
	! queued.  If so, don't bother generating a soft
	! interrupt.  IDN interrupts could exhaust the
	! intr_vec structs for the given cpu and that code
	! doesn't know how to survive with intr_vec structs!
	!
	ldstub	[%g6 + %g7], %g7	! idn_dmv_active = 0xff
	brz,a,pt %g7, 2f
	ldx	[%g1 + IDN_SOFT_INUM], %g7	! g7 = idn_soft_inum
	mov	-1, %g7
2:

	!
	! Setup to cause an IDN soft interrupt to occur,
	! (if necessary).
	!
	set	dmv_finish_intr, %g3
	jmp	%g3
	mov	%g7, %g1

	SET_SIZE(idn_dmv_handler)

#endif /* lint */