/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */

#ifndef lint
static char    *rcsid = "$Header:rompint.c 12.0$";
#endif

#include <dos.h>
#include <sys\types.h>
#include "pcparam.h"
#include "rb.h"
#include "trace.h"
#include "vars.h"

#define SEOI	0x60		/* specific EOI command */
#define R_INTSET(lvl) (0x80 >> (lvl))	/* if given level is set */

struct intque   intque3[NCPU] = {0};
struct intque   intque4[NCPU] = {0};

/************************************************
 * rompint3(3, irqlvl, fromintlvl,quereq,cbcb) - Generate a romp level 3 interrupt
 *   irqlvl     - The irq level of the I/O interrupt
 *   fromintlvl - Tells us if we are being called directly from
 *                interrupt level.
 *   quereq     - Tells us if the interrupt is from the queue.
 */

#define MAXEOIWAIT 500000L

u_short qeoi = EOIPENDING;


void rompint3(lvl, irqlvl, fromintlvl, quereq, cbcb, old_stack)
	int		lvl;
	int             irqlvl;
	int             fromintlvl;
	int             quereq;
	struct cbcb    *cbcb;
	struct i_stack far *old_stack;
{
	register struct intque *qptr;
	register int    i;
	u_short         s;
	u_long          eoitimeout;
	int             irq;
	int             eoipending = 0;

	if (cbcb == 0)
		cbcb = cbcbptr;		/* use master by default */
	qptr = &intque3[cbcb->cpu];
	s = spl();

#ifdef DEBUG
	TRACE(TRACE_INT, &lvl, 4 * sizeof (int));
	if (irqlvl == 0xff) {
		short	buffer[5];
		short far *sp = (short far *)&old_stack->IP;

		for (i=2; i < 5; i++) {
			buffer[i] = *sp++;
		}
		buffer[0] = FP_OFF(old_stack);
		buffer[1] = FP_SEG(old_stack);
		TRACE(TRACE_HDWINT, buffer, sizeof(buffer));
	}
#endif /* DEBUG */
		
	/*
	 * Since the ROMP->PS/2 interrupt is currently set at IRQ7 we can run
	 * into problems during periods of intense interrupt forwarding
	 * (asy). What happens is that the IRQ7 is continually preempted by
	 * higher priority interrupts. To avoid this we check for a pending
	 * request whenever we are called (we also check later on when we are
	 * waiting to send an EOI) 
	 */

	if ((fromintlvl) && (option_flag & OPTION_CBCB) == 0 && (cbcb->op_code != 0))
		cbcbreq(old_stack);

	/*
	 * Check to see if we are forwarding this interrupt. If we are NOT
	 * then send an EOI (if required) and exit. 
	 */

	irq = irqlvl & 0x0007;
	if (cbcb->vec_map[irq] == 0)
	{
		if (fromintlvl)
			eoi_master(irq+SEOI);
		splx(s);
		return;
	}
	/*
	 * Next check to see if the PCIF rompint 3 bit is busy. 
	 */
	i = IOIN(cbcb->port+R_INTR);
	if (i & R_INT3SET)
	{
		/*
		 * If it is then we queue the interrupt 
		 */
		if (quereq == 0)
			i = queint(3, irqlvl, fromintlvl, qptr, cbcb);
		else
			i = -1;
		splx(s);
		return;
	}
	/*
	 * Otherwise check to see if there are any queued interrupt pending,
	 * if so then queue this one and send the next one from the queue. 
	 */

	if ((quereq == 0) && (qptr->head != qptr->tail))
	{
		queint(3, irqlvl, fromintlvl, qptr, cbcb);
		i = qptr->head;
		irqlvl = qptr->irqlvl[i];
		fromintlvl = qptr->fromintlvl[i];
		cbcb = qptr->cbcb[i];
		i = (i + 1) % MAXINTQ;
		qptr->head = i;

		/*
		 * Check to see if we are forwarding this interrupt. If we
		 * are NOT then send an EOI (if required) and exit. 
		 */

		irq = irqlvl & 0x0007;
		if (cbcb->vec_map[irq] == 0)
		{
			if (fromintlvl)
				eoi_master(irq+SEOI);
			splx(s);
			return;
		}
	}
	if (fromintlvl)
		eoipending = qeoi;

	cbcb->romp_int3 = LOBYTE(irqlvl) | eoipending;
	SETBIT(cbcb->port+R_INTR, R_INT3);

	if (eoipending & EOIPENDING)
	{

		/*
		 * The PS/2 Hardware does not allow alternate bus masters
		 * (ROMP) to address io addresses 0x00 to 0x100, so we must
		 * generate the EOI for the Unix interrupt handler. 
		 *
		 * The sequence of events must go like this: 
		 *
		 * Catch the interrupt 
		 *
		 * forward it to Unix 
		 *
		 * Wait for the cbcb->romp_int3 field to go to 0 this is done in
		 * the int_8259 routine in Unix (see autoconf.c) AFTER the
		 * device specific interrupt handler returns. 
		 *
		 * Send an EOI to the 8259 
		 *
		 * Return from interrupt processing. 
		 */

		eoitimeout = 0;
		while ((cbcb->romp_int3 & 0xff) && (eoitimeout++ < MAXEOIWAIT))
			if (cbcb->op_code != 0 && (option_flag & OPTION_CBCB) == 0)
				cbcbreq(old_stack);

#ifdef DEBUG
		if (eoitimeout >= MAXEOIWAIT)
			screen_trace('G');
#endif /* DEBUG */

		eoi_master(irq+SEOI);

	}			/* End if(fromintlvl) */
	splx(s);
}


void rompint4(lvl, irqlvl, fromintlvl, quereq, cbcb, old_stack)
	int		lvl;
	int             irqlvl;
	int             fromintlvl;
	int             quereq;
	struct cbcb    *cbcb;
	struct i_stack far *old_stack;
{
	register struct intque *qptr;
	register int    i;
	u_short         s;
	u_long          eoitimeout;
	int             irq;
	int             eoipending = 0;

	if (cbcb == 0)
		cbcb = cbcbptr;		/* use master by default */
	qptr = &intque4[cbcb->cpu];
	s = spl();

#ifdef DEBUG
	TRACE(TRACE_INT, &lvl, 4 * sizeof (int));
	if (irqlvl == 0xff) {
		short	buffer[5];
		short far *sp = (short far *)&old_stack->IP;

		for (i=3; i < 5; i++) {
			buffer[i] = *sp++;
		}
		buffer[0] = FP_OFF(old_stack);
		buffer[1] = FP_SEG(old_stack);
		TRACE(TRACE_HDWINT, buffer, sizeof(buffer));
	}
#endif

	/*
	 * Since the ROMP->PS/2 interrupt is currently set at IRQ7 we can run
	 * into problems during periods of intense interrupt forwarding
	 * (asy). What happens is that the IRQ7 is continually preempted by
	 * higher priority interrupts. To avoid this we check for a pending
	 * request whenever we are called (we also check later on when we are
	 * waiting to send an EOI) 
	 */

	if ((fromintlvl) && (cbcb->op_code != 0))
		cbcbreq(old_stack);

	/*
	 * Check to see if we are forwarding this interrupt. If we are NOT
	 * then send an EOI (if required) and exit. 
	 */

	irq = (irqlvl & 0x0007);
	if (cbcb->vec_map[irq+8] == 0)
	{
		if (fromintlvl)
			eoi_slave(irq+SEOI);
		splx(s);
		return;
	}
	/*
	 * Next check to see if the PCIF rompint 4 bit is busy. 
	 * if we can queue interrupts we do so, otherwise
	 * we wait until we can send this interrupt 
	 */
	while (IOIN(cbcb->port+R_INTR) & R_INT4SET)
	{
		/*
		 * If it is then we queue the interrupt 
		 */
		if (quereq == 0)
		{
			queint(4, irqlvl, fromintlvl, qptr, cbcb);
			splx(s);
			return;
		}
		if (cbcb->op_code != 0)
			cbcbreq(old_stack);
	}
	/*
	 * Otherwise check to see if there are any queued interrupt pending,
	 * if so then queue this one and send the next one from the queue. 
	 */

	if ((quereq == 0) && (qptr->head != qptr->tail))
	{
		queint(4, irqlvl, fromintlvl, qptr, cbcb);
		i = qptr->head;
		irqlvl = qptr->irqlvl[i];
		fromintlvl = qptr->fromintlvl[i];
		cbcb = qptr->cbcb[i];
		i = (i + 1) % MAXINTQ;
		qptr->head = i;

		/*
		 * Check to see if we are forwarding this interrupt. If we
		 * are NOT then send an EOI (if required) and exit. 
		 */

		irq = irqlvl & 0x0007;
		if (cbcb->vec_map[irq+8] == 0)
		{
			if (fromintlvl)
				eoi_slave(irq+SEOI);
			splx(s);
			return;
		}
	}
	if (fromintlvl)
		eoipending = qeoi;

	cbcb->romp_int4 = LOBYTE(irqlvl) | eoipending;
	SETBIT(cbcb->port+R_INTR, R_INT4);

	if (eoipending & EOIPENDING)
	{

		/*
		 * The PS/2 Hardware does not allow alternate bus masters
		 * (ROMP) to address io addresses 0x00 to 0x100, so we must
		 * generate the EOI for the Unix interrupt handler. 
		 *
		 * The sequence of events must go like this: 
		 *
		 * Catch the interrupt 
		 *
		 * forward it to Unix 
		 *
		 * Wait for the cbcb->romp_int4 field to go to 0 this is done in
		 * the int_8259 routine in Unix (see autoconf.c) AFTER the
		 * device specific interrupt handler returns. 
		 *
		 * Send an EOI to the 8259 
		 *
		 * Return from interrupt processing. 
		 */

		eoitimeout = 0;
		while ((cbcb->romp_int4 & 0xff) && (eoitimeout++ < MAXEOIWAIT))
			if (cbcb->op_code != 0)
				cbcbreq(old_stack);

#ifdef DEBUG
		if (eoitimeout >= MAXEOIWAIT)
			screen_trace('G');
#endif /* DEBUG */

		eoi_slave(irq+SEOI);

	}			/* End if(fromintlvl) */
	splx(s);
}

/*
 * The following routine gets called 18.2 times per second, and can be
 * used to special timer functions as needed.
 * Currently it is only used for the speaker, and for insuring that
 * keyboard scan codes are passed even when the main loop is busy
 * (e.g. in bios).
 */
extern int can_restart;
extern int loop_count;
extern u_short real_stack;
extern struct XCLOCK xclock;
#define MAX_LOOP_COUNT	546	/* 30 seconds */

sysclock(old_stack)
	struct i_stack far *old_stack;
{
	speaker_int();		/* handle the speaker timeout */
	check_kbd(0);		/* check to see if kbd needs forwarding */
#ifdef ABIOS
	hdtimer();		/* hard disk timeout/delay routine */
	fdtimer();		/* floppy timeout/delay routine */
#endif /* ABIOS */

	if ((can_restart) && (loop_count++ > MAX_LOOP_COUNT)) {
		char far *tmp_addr = (char far *)&can_restart;
		short far *sp;
		int	i;
		struct dos_sys_stack far *dos_stack =
			(struct dos_sys_stack far *) ((u_long)FP_SEG(old_stack) << 16 | (u_long)old_stack->d_stackptr);

		rbprintf("HELP:main loop timeout\n");
		rbprintf("Old stack = %lx\n",old_stack);
		sp =(short far *)dos_stack;
		dos_stack =((struct sys_stack_save far *)dos_stack)->saved_stack;
		rbprintf("original stack = %lx\n",dos_stack);
		rbprintf("Registers are: \n");
		rbprintf("AX=%x  SI=%x  CS=%x  IP=%x\n", dos_stack->AX,
				old_stack->SI,dos_stack->CS,dos_stack->IP);
		rbprintf("BX=%x  DI=%x  DS=%x  FLGS=%x\n",old_stack->BX,
		    		old_stack->DI,old_stack->b_DS,dos_stack->flags);
		rbprintf("CX=%x  SP=%x  ES=%x\n",old_stack->b_CX,
				FP_OFF(dos_stack),dos_stack->ES);
		rbprintf("DX=%x  BP=%x  SS=%x\n",old_stack->b_DX,
					dos_stack->BP,FP_SEG(dos_stack));
		rbprintf("Unix segments are: code %x data %x stack %x (%x)\n",
			getcs(),FP_SEG(tmp_addr),getss(),real_stack);
		rbprintf(" hang detected at %d:%d:%d\n",xclock.hrs,xclock.min,
								xclock.sec);
#ifdef DONTGIVEUP
		loop_count = 0;
		/*
		 * try to jump over offending instruction
		 */
		dos_stack->IP += 4;	/* jump over a mov word ptr DS:[BX],0 */
#else
		can_restart = 0;
		rompint0();
#endif /* GIVEUP */
	}
}


static
int queint(romplvl, irqlvl, fromintlvl, qptr, cbcb)
	int             romplvl;
	int             irqlvl;
	int             fromintlvl;
	struct intque  *qptr;
	struct cbcb    *cbcb;
{
	register int    i;
	int             rval = 0;


	TRACE(TRACE_QINT, &romplvl, 4 * sizeof (int));
	i = qptr->tail;
	qptr->irqlvl[i] = irqlvl;
	qptr->fromintlvl[i] = fromintlvl;
	qptr->cbcb[i] = cbcb;
	i = (i + 1) % MAXINTQ;
	if (i != qptr->head)
		qptr->tail = i;
	else
	{
		rbprintf("Queue overflow romp lvl=%d irqlvl=%d fromintlvl=%d\n",
		       romplvl, irqlvl, fromintlvl);
		rval = -1;
	}

	return (rval);

}

rompint0()
{
	screen_cbcb->romp_int0 = 1;	/* flag as a PC generated interrupt */
	SETBIT(screen_cbcb->port+R_INTR, R_INT0);
	delay(1);
	RSETBIT(screen_cbcb->port+R_INTR, R_INT0);
}
