/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: archdep.c 1.44 90/06/27 $"

/*
 * archdep.c
 *	Architecture dependent routines.
 *
 * Balance/Symmetry version.
 */

/* $Log:	archdep.c,v $
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/mutex.h"
#include "sys/systm.h"
#include "sys/user.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/vm.h"
#include "sys/buf.h"
#include "sys/boot.h"
#include "sys/conf.h"
#include "sys/vfs.h"
#include "sys/vnode.h"
#include "sys/ufsmount.h"
#include "sys/cmn_err.h"
#include "sys/var.h"

#include "sys/cfg.h"
#include "sys/engine.h"
#include "sys/slic.h"
#include "sys/slicreg.h"
#include "sys/clkarb.h"
#include "sys/clock.h"
#include "sys/arch.h"

#include "sys/inline.h"
#include "sys/pte.h"
#include "sys/intctl.h"
#include "sys/plocal.h"
#include "sys/hwparam.h"
#include "sys/mftpr.h"
#ifdef	MODELD
#include "sys/scan.h"
#endif	/* MODELD */

#define	masksig(s)	(1<<((s)-1))

#define SYNC_TIMEOUT	10 	/* sync timeout in seconds */

int	sync_timeout;		/* ticks to timeout disk sync on reboot */
int	bufbusy, obufbusy;	/* Used when syncing disks. */

int	cs_tid;			/* check sys timer id */
int	wd_tid;			/* watchdog timer id */

int	ndiskoffline;		/* # of offline / unusable disk drives */
lock_t	diskoff_lock;		/* Synchronizes access to ndiskoffline */

lock_t	uncmem_lock;
extern	int	memintvl;	/* polling interval in Hz */
extern	short	upyet;		/* Set when 1st processor is initialized */

/*
 * physstrat()
 *	Start an IO given buffer pointer and driver strategy routine.
 *
 * Wait for the IO to complete unless it's a "dirty" page write.
 *
 * Used by physio().
 */

physstrat(bp, dp, prio)
	struct buf *bp;
	struct dev *dp;
	int	prio;
{
	int	flags;

	flags = bp->b_flags;
	BIODONE(bp) = 0;				/* new IO starting */
	DEV_STRATEGY(dp, bp, (daddr_t)0);

	/*
	 * pageout IO doesn't wait for pushed pages.
	 * Note: use `flags' since don't want to talk to bp after give it
	 * to driver strat routine.
	 */

	if (flags & B_DIRTY)
		return;

	(void)p_sema(&bp->b_iowait, prio);
}

/*
 * physstratvp()
 *	Start an IO given buffer pointer and vnode strategy routine.
 *
 * Wait for the IO to complete unless it's a "dirty" page write.
 * The vnode used to select the strategy routine is passed in bp->b_vp.
 *
 * Used by swapio().
 */

physstratvp(bp, prio)
	struct buf *bp;
	int	prio;
{
	int	flags;

	flags = bp->b_flags;
	BIODONE(bp) = 0;				/* new IO starting */
	VOP_STRATEGY(bp);

	/*
	 * pageout IO doesn't wait for pushed pages.
	 * Note: use `flags' since don't want to talk to bp after give it
	 * to driver strat routine.
	 */

	if (flags & B_DIRTY)
		return;

	(void)p_sema(&bp->b_iowait, prio);
}

/*
 * disk_online()
 *	A disk has come online.
 *
 * Decrement "ndiskoffline" and turn on FP_IO_ONLINE if fp_lights set.
 */

disk_online()
{
	spl_t	s_ipl;

	if (upyet)
		s_ipl = p_lock(&diskoff_lock, SPLHI);

	--ndiskoffline;
	if (ndiskoffline == 0 && fp_lights)
		FP_IO_ONLINE;

	if (upyet)
		v_lock(&diskoff_lock, s_ipl);
}

/*
 * disk_offline()
 *	A disk has gone offline.
 * Increment "ndiskoffline" and hit FP_IO_OFFLINE if fp_lights set.
 */

disk_offline()
{
	spl_t	s_ipl;

	if (upyet)
		s_ipl = p_lock(&diskoff_lock, SPLHI);

	++ndiskoffline;
	if (fp_lights)
		FP_IO_OFFLINE;

	if (upyet)
		v_lock(&diskoff_lock, s_ipl);
}

/*
 * cmn_err() is a generic routine used to display kernel messages on
 * the console or the user's terminal.  There are five classes of
 * messages:
 *	CE_TO_USER:
 *		send message to user's terminal.  The caller is
 *		responsible for ensuring that the calling context
 *		is appropriate for sending messages to the user's
 *		terminal.
 *
 *	CE_CONT:
 *		This is used to display additional lines of a
 *		multi-line kernel message.
 *
 *	CE_NOTE:
 *		This class is used to display messages which report
 *		unusual kernel events which are non-fatal and require
 *		no user intervention. (e.g. stray interrupts)
 *
 *	CE_WARN:
 *		This class is used to inform the administrator that
 *		the system is in a state which may be causing poor
 *		performance or unexpected application failures.
 *		Typically, these problems can be fixed by
 *		reconfiguring the kernel.  (e.g. table overflows )
 *
 *	CE_PANIC:
 *		An unresolvable fatal error has occurred.  the
 *		 system is being shut down, and will reboot.
 * 		If we are called twice, then we avoid trying to
 *		sync the disks as this often leads to recursive panics.
 * 		If we are called by multiple processors concurrently, the first
 * 		one attempts to sync disk, the rest print a message and pause
 *		 themselves.  All register vars are saved on stack.
 */

/*
 * In case console is off, panicstr contains argument to last call to panic.
 */

#define ARGS	a0,a1,a2,a3,a4,a5,a6,a7,a8,a9

char	*panicstr;
int	*panicargs;
lock_t	panic_lock;		/* protect against concurrent panics */
bool_t	dblpanic = 0;		/* double panic? */
struct panic_data panic_data;	/* collection of panic info */

#ifndef KLINT
/*VARARGS2*/
cmn_err(level, fmt, ARGS)
	int level;
	char *fmt;
	int ARGS;
{
#ifdef	i386
	register caddr_t stk_ptr;
	register int i;
	register int bootopt;
	register struct engine *eng;
	register spl_t	s_ipl;
#endif	/* i386  */
#ifdef	ns32000
	register spl_t	s_ipl;
	register int bootopt;
	register struct engine *eng;
	register int i;
	register caddr_t r3;	/* Known to be r3 */
#endif	/* ns32000  */
	bool_t	stillalive;
#ifdef lint
	extern unsigned READ_PTROOT();
#endif /*lint*/

	/*
	 * Save stack pointer. This code not placed in "boot"
	 * routine because it is necessary to save the scratch
	 * registers for concurrently panicing processors.
	 * l.panicsp points to the a copy of the fp on the stack.
	 * Also save page-table in use by this processor when the
	 * system went down.
	 */

#ifdef	i386
	stk_ptr = (caddr_t) saveregs();
#endif	/* i386  */

#ifdef	ns32000
#ifdef lint
	r3 = (caddr_t)0;
#else
	;asm("save	[r0,r1,r2,r3,r4,r5,r6,r7]");
	;asm("sprd	fp,tos");
	;asm("sprd	sp,r3");
#endif	/* lint  */
#endif	/* ns32000  */

	switch (level) {
	case CE_TO_USER:
		uprintf(fmt, ARGS);
		break;

	case CE_CONT :
		printf(fmt, ARGS);
		break;

	case CE_NOTE :
		printf("\nNOTICE: ");
		printf(fmt, ARGS);
		printf("\n");
		break;

	case CE_WARN :
		printf("\nWARNING: ");
		printf(fmt, ARGS);
		printf("\n");
		break;

	case CE_PANIC : 
		bootopt = RB_AUTOBOOT;
		if (!upyet) {
			panicstr = fmt;
			panicargs = &a0;
			printf("\nPANIC: ");
			printf(fmt, ARGS);
			printf("\n");
			printstack();
#ifdef i386
#ifdef	MODELD
			if ((l.eng->e_flags&E_SGS2) == 0)
#endif	/* MODELD */
			dump_SGShw_regs();
#endif	/* i386  */
			FW_BOOT(RB_PANIC, bootopt);
		}
		
		if ((!panic_data.pd_engine) && (s_ipl = cp_lock(&panic_lock, SPLHI)) != CPLOCKFAIL) {
			panic_data.pd_engine = l.eng;
#ifdef	i386
			panic_data.pd_sp = stk_ptr;
			l.panicsp = stk_ptr;
			l.panic_pt = READ_PTROOT();
#endif	/* i386  */
#ifdef	ns32000
			panic_data.pd_sp = r3;
			l.panicsp = r3;
#endif	/* ns32000  */
			if (l.usingfpu)
				save_fpu();
#ifdef FPA
			/* if have fpa, not idle and using it */
			if (l.fpa && !l.noproc && u.u_procp->p_fpa)
				save_fpa();
#endif	/* FPA  */
			panicstr = fmt;
			panicargs = &a0;
			if (!l.noproc)
				panic_data.pd_proc = u.u_procp;
			/*
			 * Send nmi to all other engines.
			 * Keep trying until all respond - for robustness.
			 */
			for (i = 0; i < 100; i++) {
				stillalive = 0;
				for (eng=engine; eng < engine_Nengine; eng++) {
					if ((eng != l.eng)
					&& ((eng->e_flags&E_OFFLINE) != E_OFFLINE)
					&& ((eng->e_flags&E_PAUSED) != E_PAUSED)) {
						stillalive++;
						nmIntr(eng->e_slicaddr, PAUSESELF);
					}
				}
				if (stillalive == 0)
					break;
#ifdef	i386
				DELAY(40000);
#endif	/* i386  */
#ifdef	ns32000
				DELAY(10000);
#endif	/* ns32000  */
			}
			v_lock(&panic_lock, s_ipl);
			printf("\nPANIC: ");
			printf(fmt, ARGS);
			printf("\n");
			printstack();
#ifdef i386
#ifdef	MODELD
			if ((l.eng->e_flags&E_SGS2) == 0)
#endif	/* MODELD */
			dump_SGShw_regs();
#endif	/* i386  */
		} else {	/* Panic in progress */
			if (l.eng == panic_data.pd_engine) {
				bootopt |= RB_NOSYNC;
				dblpanic = 1;
#ifdef	i386
				panic_data.pd_dblsp = stk_ptr;
#endif	/* i386  */
#ifdef	ns32000
				panic_data.pd_dblsp = r3;
#endif	/* ns32000  */
				printf("\nDOUBLE PANIC: ");
				printf(fmt, ARGS);
				printf("\n");
			} else {
				/* multiple processors in panic */
				(void) splhi();
				printf("\nCONCURRENT PANIC: ");
				printf(fmt, ARGS);
				printf("\n");
				pause_self();
			}
		}
		
		/*
		 * Return to Firmware
		 */
		FW_BOOT(RB_PANIC, bootopt);
		/*NOTREACHED*/

	default :
		cmn_err(CE_PANIC,
		  "unknown level in cmn_err (level=%d, msg=\"%s\")",
		  level, fmt, ARGS);
		/*
		 *+ an invalid level argument has been passed to the
		 *+ cmn_err routine.
		 */
	}

	/*
	 * gotta put the stack back in shape before exit. This restores
	 * the registers that were saved by saveregs() earlier.
	 */
#ifdef i386
	;asm("popal");
#endif /* i386 */
#ifdef ns32000
	/* This isn't tested yet */
	;asm("movl	tos, r0");
	;asm("restore	[r0,r1,r2,r3,r4,r5,r6,r7]");
#endif /* ns32000 */
}
#endif /*KLINT*/

/*
 * flush_intr()
 *	Flush pending interrupts.
 *
 * Used when shutting down processor to insure pending interrupts
 * are cleared (and handled).
 */

flush_intr()
{
	int	counter;

	/*
	 * While there is a pending (HW or SW) interrupt, open
	 * a window to let it in.
	 */

	SLICPRI(0);				/* try not to win arbitration */

	for (;;) {
		if ((va_slic->sl_ictl & (SL_HARDINT|SL_SOFTINT)) == 0)
			break;
		(void) spl0();
		for (counter = 0; counter < 10; counter++)
			continue;		/* window to take int */
		(void) splhi();
	}
}

/*
 * tod_sync()
 *	TOD clock handler when syncing disks.
 *
 * This routine will, if necessary, timeout the disk sync.
 * Thus, during panic situations, there will be a higher probability
 * of returning to FW.
 */
void
tod_sync()
{

	extern void todclock();

	/*
	 * Increment time as usual.
	 */

	todclock();

	/*
	 * If already sync'ed then take no action.
	 * If number of buffers to sync hasn't changed for sync_timeout
	 * ticks, then give up and return to firmware.
	 */

	if (bufbusy == 0)
		return;
	if (bufbusy != obufbusy) {
		obufbusy = bufbusy;
		sync_timeout = SYNC_TIMEOUT * hz;
		return;
	}
	if (--sync_timeout == 0) {
		cmn_err(CE_NOTE, "Sync timeout\n");
		/*
		 *+ During a panic or shutdown sequence, the kernel's
		 *+ synch of disk cache information to the hard disk
		 *+ didn't complete before timing out.  This might be
		 *+ due to a kernel software or hardware problem.
		 *+ The result can be more filesystem corruption than
		 *+ would have been the case had the synch finished.
		 */
		return_fw();
	}
}


/*
 * reboot_sync()
 * 	Sync the disks prior to returning to Firmware.
 *	May be overridden.  Assume we're the only
 *	active processor.
 */
reboot_sync(howto)
	int howto;
{
	register struct buf *bp;
	int nbusy;
	extern void (*irq_table[])();

	(void) spl1();
	if ((howto&RB_NOSYNC)==0 && mounttab[0].m_bufp) {
		cmn_err(CE_NOTE, "syncing disks... ");
		/*
		 *+ A panic or shutdown sequence is in operation and the system
		 *+ has begun synching disk information to the disk.
		 */

		/*
		 * Take over TOD clock ticks...
		 */
		(void) splhi();
		sync_timeout = SYNC_TIMEOUT * hz;
		obufbusy = bufbusy = v.v_buf;	/* assume v.v_buf busy buffers */
		if (arch_type == SYSTYP_S1)
			irq_table[0] = tod_sync;
		else
			int_bin_table[TODCLKBIN].bh_hdlrtab[TODCLKVEC] = tod_sync;
		(void) spl1();

		/*
		 * Should the following three calls be replaced by a single
		 * call to sync()?  Probably, as sync will call all three in
		 * the same order.  The only differences are that if a sync is
		 * already in progress, the new one will return, and all other
		 * vfs's will get their sync routines called as well (update is
		 * also ufs-sync)
		 */
		drivers_sync();
		update();
		bflush(NULLVP);

		/*
		 * Loop until the sync completes. tod_sync() will
		 * return to firmware if the sync cannot complete.
		 */
		for (;;) {
			nbusy = 0;
			for (bp = &buf[v.v_buf]; --bp >= buf; ) {
				if (bp->b_flags & B_INVAL)
					continue;
				if (!BAVAIL(bp))
					nbusy++;
			}
			if (nbusy != bufbusy) {
				bufbusy = nbusy;
				if (nbusy == 0)
					break;
				cmn_err(CE_CONT, "%d ", nbusy);
			}
		}
		cmn_err(CE_CONT, "done\n");
	}
}

/*
 * return_fw()
 *	Return to Firmware.
 *	Prior to doing so, invoke custom
 *	panic handlers (if panicing).
 */

return_fw()
{
	register int	i;
	extern	int	(*cust_panics[])();

	if (upyet)
		(void) splhi();

	/*
	 * If a panic, call custom panic handlers.
	 */

	if (panicstr != NULL)
		for (i = 0; cust_panics[i] != NULL; i++)
			(*cust_panics[i])();

	FW_RETURN_FW();
	/*NOTREACHED*/
}
/*
 * shutdown_light_show()
 *	Turn off processor activity lights and light show.
 */
shutdown_light_show()
{
	register struct ctlr_toc *toc;
	register int	i;
	extern	bool_t	light_show;

	/*
	 * Get table of contents pointer for processor board.
	 */
#ifdef	ns32000
	toc = &CD_LOC->c_toc[SLB_PROCBOARD];		/* 032 processors */
#endif	/* ns32000 */

#ifdef	i386
	toc = &CD_LOC->c_toc[SLB_SGSPROCBOARD];		/* SGS processors */
#endif	/* i386 */

	/*
	 * Turn off light show - if enabled.
	 * Since panic may be called before initialization is complete,
	 * all front panel processor lights are turned off.
	 */

	if (light_show) {
		if (fp_lights) {
			for (i = 0; i < toc->ct_count; i++)
				FP_LIGHTOFF(i);
		}
#if	defined(ns32000)
		if (fp_lights <= 0)
			(void) rdslave(va_slic->sl_procid, SL_P_LIGHTOFF);
#else
		*(int *) VA_LED = 0;
#endif
	}
}


/*
 * access_error()
 *	Access Error Reporting:
 *		Bus timeouts
 *		ECC Uncorrectable
 *		Processor fatal error (SGS only)
 *
 * Called from NMI handler, SEC_error and MBAd_error with
 * copy of Access error register.
 *
 * Called at SPLHI.
 */

access_error(errval)
	unchar errval;
{
	register int io_access;
	register char *s;
	unchar acctype;
	extern	int	mem_tid;

	cmn_err(CE_WARN, "Access Error Register = 0x%x\n", errval);
	/*
	 *+ An access error has occurred.  More information, such as
	 *+ the type of access error, follows.
	 */
	errval = ~errval;
	acctype = errval & SLB_ATMSK;
	io_access = errval & SLB_AEIO;

	switch (acctype) {
	case SLB_AEFATAL:
			s = "Fatal";
		break;
	case SLB_AENONFAT:
		if (io_access)
			s = "Non-Fatal";
		else
			s = "Ecc Correctable";
		break;
	case SLB_AETIMOUT:
		s = "Timeout";
		break;
	default:
		s = "Unknown";
		break;
	}
	cmn_err(CE_CONT, "%s error on %s %s.\n", s,
		(errval & SLB_AEIO) ? "I/O" : "memory",
		(errval & SLB_AERD) ? "read" : "write");

	/*
	 * If memory error get more data...
	 */

	if ((acctype == SLB_AEFATAL) && (io_access != SLB_AEIO)) {
		if (upyet) {
			/*
			 * Avoid races with memory polling.
			 */
			if ( mem_tid != 0 ){
				untimeout(mem_tid);
				mem_tid = 0;
			}
			/*
			 * If concurrent access errors, the loser of the
			 * race commits suicide.
			 *
			 * Since we are about to die, do not bother releasing
			 * lock.
			 */
			if (cp_lock(&uncmem_lock, SPLHI) == CPLOCKFAIL) {
				cmn_err(CE_WARN, "Concurrent Access Error\n");
				/*
				 *+ A concurrent access error has occurred.
				 *+ That is, another processor received a prior
				 *+ access error and
				 *+ the system is already on the way down.
				 */
				pause_self();
				/*NOTREACHED*/
			}
		}
		memlog();
	}
}
#ifdef	MODELD
/*
 * Same as above, for modelD.
 */
sgs2_access_error(flag)
	int flag;
{
	register char *s;
	extern	memerr();
	extern	int	mem_tid;

	/*
	 *+ An access error has occurred.  More information, such as
	 *+ the type of access error, follows.
	 */

	if (isfatal(flag))
		s = "Fatal";
	else if (isnonfatal(flag)) {
		if (isio(flag))
			s = "Non-Fatal";
		else
			s = "Ecc Correctable";
	} else if (istimeout(flag))
		s = "Timeout";
	else
		s = "Unknown";

	cmn_err(CE_CONT, "%s error on %s %s.\n", s,
		isio(flag) ? "I/O" : "memory",
		isread(flag) ? "read" : "write");

	/*
	 * If memory error get more data...
	 */

	if (isfatal(flag) && !isio(flag)) {
		if (upyet) {
			/*
			 * Avoid races with memory polling.
			 */
			if (mem_tid != 0){
				untimeout(mem_tid);
				mem_tid = 0;
			}
			/*
			 * If concurrent access errors, the loser of the
			 * race commits suicide.
			 *
			 * Since we are about to die, do not bother releasing
			 * lock.
			 */
			if (cp_lock(&uncmem_lock, SPLHI) == CPLOCKFAIL) {
				cmn_err(CE_WARN, "Concurrent Access Error\n");
				/*
				 *+ a concurrent access error has ocurred.
				 *+ i.e., another processor took an
				 *+ access error before this one and
				 *+ the system's already on the way down.
				 */
				pause_self();
				/*NOTREACHED*/
			}
		}
		memlog();
	}
}
#endif	/* MODELD */

/*
 * Procedures to verify integrity of system.
 *   check_sys() is called early in main() by the init process.
 *   good_sys() is called by the *REAL* /etc/init.
 */

#define	INIT_GRACE	25*60*hz

check_sys(firsttime)
{
	if (!firsttime) {
		cmn_err(CE_WARN,"Bogus init detected, returning to single user mode.\n");
		/*
		 *+ The init process has not followed the protocol
		 *+ used by the kernel to identify it as a valid
		 *+ init process.  The system is being returned to
		 *+ single user mode.  Corrective action:
		 *+ replace the /etc/init binary with the version distributed
		 *+ with the operating system.
		 */
		psignal(&proc[1], SIGBUS);
	}
	cs_tid = timeout(check_sys, (caddr_t)0, INIT_GRACE);
}

good_sys()
{
	untimeout( cs_tid );
}
