code:
/*	$OpenBSD: eisa_machdep.c,v 1.3 2014/07/12 18:44:42 tedu Exp $	*/

/*
 * Copyright (c) 2012 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/malloc.h>
#include <sys/queue.h>

#include <machine/bus.h>
#include <sgi/localbus/intreg.h>
#include <sgi/localbus/intvar.h>

#include <dev/ic/i8259reg.h>
#include <dev/isa/isareg.h>
#include <dev/eisa/eisavar.h>

#define	IO_EXTNMI	0x0461
#define	IO_ELCR0	0x04d0
#define	IO_ELCR1	0x04d1

#define	IRQ_CASCADE	2

int	eisa_intr(void *);

/*
 * Magic registers (from IRIX IP22.h). They appear in EISA I/O space.
 */

#define	EIU_MODE_REG	0x0001ffc0
#define	EIU_STAT_REG	0x0001ffc4
#define	EIU_PREMPT_REG	0x0001ffc8
#define	EIU_QUIET_REG	0x0001ffcc
#define	EIU_INTRPT_ACK	0x00010004

#define	eiu_read(o) \
	*(volatile uint32_t *)PHYS_TO_XKPHYS(EISA_IO_BASE | (o), CCA_NC)
#define	eiu_write(o,v) \
	*(volatile uint32_t *)PHYS_TO_XKPHYS(EISA_IO_BASE | (o), CCA_NC) = (v)

#define	eisa_io_read(o) \
	*(volatile uint8_t *)PHYS_TO_XKPHYS(EISA_IO_BASE | (o), CCA_NC)
#define	eisa_io_write(o,v) \
	*(volatile uint8_t *)PHYS_TO_XKPHYS(EISA_IO_BASE | (o), CCA_NC) = (v)

#define	EISA_INT2_IRQNO	INT2_MAP1_INTR(INT2_MAP_EISA)

/*
 * EISA interrupt handlers.
 */

#define	EISA_NINTS	16

struct eisa_intrhand {
	SLIST_ENTRY(eisa_intrhand)	  ei_list;
	int				(*ei_fn)(void *);
	void				 *ei_arg;
	int				  ei_type;

	int				  ei_intr;
	struct evcount			  ei_evcnt;
};

SLIST_HEAD(, eisa_intrhand) eisa_ih[EISA_NINTS];

int
eisa_intr_map(eisa_chipset_tag_t ec, u_int irq, eisa_intr_handle_t *ihp)
{
	*ihp = -1;

	if (irq >= EISA_NINTS)
		return 1;

	if (irq == IRQ_CASCADE)	/* cascade */
		irq = 9;

	*ihp = irq;
	return 0;
}

const char *
eisa_intr_string(eisa_chipset_tag_t ec, eisa_intr_handle_t ih)
{
	static char irqstr[32];

	snprintf(irqstr, sizeof irqstr, "eisa irq %d", ih);
	return irqstr;
}

void *
eisa_intr_establish(eisa_chipset_tag_t ec, eisa_intr_handle_t ih, int type,
    int level, int (*func)(void *), void *arg, char *what)
{
	struct eisa_intrhand *first = NULL, *eih;

	if (ih >= EISA_NINTS || ih == IRQ_CASCADE)
		return NULL;

	if (!SLIST_EMPTY(&eisa_ih[ih])) {
		first = SLIST_FIRST(&eisa_ih[ih]);
		/* can't share edge and level interrupts */
		if (first->ei_type != type)
			return NULL;
	}

	eih = malloc(sizeof(*eih), M_DEVBUF, M_NOWAIT);
	if (eih == NULL)
		return NULL;

	eih->ei_fn = func;
	eih->ei_arg = arg;
	eih->ei_type = type;
	eih->ei_intr = ih;
	evcount_attach(&eih->ei_evcnt, what, &eih->ei_intr);

	if (first == NULL) {
		/* Update ELCR */
		if (type == IST_LEVEL) {
			if (ih >= 8)
				eisa_io_write(IO_ELCR1,
				    eisa_io_read(IO_ELCR1) | (1 << (ih & 7)));
			else
				eisa_io_write(IO_ELCR0,
				    eisa_io_read(IO_ELCR0) | (1 << ih));
		}
		/* Update OCW */
		if (ih >= 8)
			eisa_io_write(IO_ICU2 + PIC_OCW1,
			    eisa_io_read(IO_ICU2 + PIC_OCW1) & ~(1 << (ih & 7)));
		else
			eisa_io_write(IO_ICU1 + PIC_OCW1,
			    eisa_io_read(IO_ICU1 + PIC_OCW1) & ~(1 << ih));
	}

	SLIST_INSERT_HEAD(&eisa_ih[ih], eih, ei_list);

	return eih;
}

void
eisa_intr_disestablish(eisa_chipset_tag_t ec, void *cookie)
{
	struct eisa_intrhand *eih = (struct eisa_intrhand *)cookie;
	unsigned int ih = (unsigned int)eih->ei_intr;

#ifdef DIAGNOSTIC
	if (ih >= EISA_NINTS || ih == IRQ_CASCADE)
		return;
#endif

	SLIST_REMOVE(&eisa_ih[ih], eih, eisa_intrhand, ei_list);

	if (SLIST_EMPTY(&eisa_ih[ih])) {
		/* Reset ELCR */
		if (ih >= 8)
			eisa_io_write(IO_ELCR1,
			    eisa_io_read(IO_ELCR1) & ~(1 << (ih & 7)));
		else
			eisa_io_write(IO_ELCR0,
			    eisa_io_read(IO_ELCR0) & ~(1 << ih));
		/* Update OCW */
		if (ih >= 8)
			eisa_io_write(IO_ICU2 + PIC_OCW1,
			    eisa_io_read(IO_ICU2 + PIC_OCW1) | (1 << (ih & 7)));
		else
			eisa_io_write(IO_ICU1 + PIC_OCW1,
			    eisa_io_read(IO_ICU1 + PIC_OCW1) | (1 << ih));
	}

	evcount_detach(&eih->ei_evcnt);
	free(eih, M_DEVBUF, 0);
}

int
eisa_intr(void *arg)
{
	struct device *dv = (struct device *)arg;
	struct eisa_intrhand *eih;
	uint irq;
	int rc, handled;

	irq = eisa_io_read(EIU_INTRPT_ACK);
	if (irq >= EISA_NINTS) {
		/* anything better to do? */
		panic("%s: unexpected irq value %08x", dv->dv_xname, irq);
		return -1;
	}

	handled = 0;
	SLIST_FOREACH(eih, &eisa_ih[irq], ei_list) {
		rc = (*eih->ei_fn)(eih->ei_arg);
		if (rc != 0) {
			handled = 1;
			eih->ei_evcnt.ec_count++;
			if (rc > 0)
				break;
		}
	}
	if (handled == 0)
		printf("%s: spurious irq %d\n", dv->dv_xname, irq);

	if (irq >= 8)
		eisa_io_write(IO_ICU2 + PIC_OCW2, OCW2_EOI);
	eisa_io_write(IO_ICU1 + PIC_OCW2, OCW2_EOI);

	return handled;
}

void
eisa_attach_hook(struct device *parent, struct device *self,
    struct eisabus_attach_args *eba)
{
	unsigned int irq;

	/*
	 * Unlock the bus. Magic sequence taken from Linux.
	 */

	eiu_write(EIU_PREMPT_REG, 0x0000ffff);
	eiu_write(EIU_QUIET_REG, 1);
	eiu_write(EIU_MODE_REG, 0x40f3c07f);

	/*
	 * Reset the bus.
	 */

	eisa_io_write(IO_EXTNMI, 0x01);
	delay(10000);
	eisa_io_write(IO_EXTNMI, 0x00);
	/* some boards need enough time to reset, or they won't probe */
	delay(250000);

	/*
	 * Setup i8259...
	 */

	/* Program PIC1. */
	eisa_io_write(IO_ICU1 + PIC_ICW1, ICW1_SELECT | ICW1_IC4);
	eisa_io_write(IO_ICU1 + PIC_ICW2, ICW2_VECTOR(0));
	eisa_io_write(IO_ICU1 + PIC_ICW3, ICW3_CASCADE(IRQ_CASCADE));
	eisa_io_write(IO_ICU1 + PIC_ICW4, ICW4_8086);
	eisa_io_write(IO_ICU1 + PIC_OCW1, 0xff);

	/* Program PIC2. */
	eisa_io_write(IO_ICU2 + PIC_ICW1, ICW1_SELECT | ICW1_IC4);
	eisa_io_write(IO_ICU2 + PIC_ICW2, ICW2_VECTOR(8));
	eisa_io_write(IO_ICU2 + PIC_ICW3, ICW3_SIC(IRQ_CASCADE));
	eisa_io_write(IO_ICU2 + PIC_ICW4, ICW4_8086);
	eisa_io_write(IO_ICU2 + PIC_OCW1, 0xff);

	/* Interrupts are edge-triggered by default. */
	eisa_io_write(IO_ELCR0, 0x00);
	eisa_io_write(IO_ELCR1, 0x00);

	/* Unmask the cascade interrupt */
	eisa_io_write(IO_ICU1 + PIC_OCW1, 0xff ^ (1 << IRQ_CASCADE));

	/*
	 * Setup interrupt handling.
	 */

	for (irq = 0; irq < EISA_NINTS; irq++)
		SLIST_INIT(&eisa_ih[irq]);

	int2_intr_establish(EISA_INT2_IRQNO, IPL_TTY, eisa_intr, self,
	    self->dv_xname);
	printf(" irq %d", EISA_INT2_IRQNO);
}

int
eisa_maxslots(eisa_chipset_tag_t ec)
{
	/*
	 * Indigo2 systems can only have up to four slots (and only three
	 * for Impact models), but slot numbering starts at 1 (#0 being
	 * the host).
	 */
	return 1 + 4;
}


code:
/*	$OpenBSD: intr_template.c,v 1.15 2015/05/01 11:17:22 miod Exp $	*/

/*
 * Copyright (c) 2009 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Common interrupt dispatcher bowels.
 *
 * This file is not a standalone file; to use it, define the following
 * macros and #include <sgi/sgi/intr_template.c>:
 *
 * INTR_FUNCTIONNAME	interrupt handler function name
 * MASK_FUNCTIONNAME	interrupt mask computation function name
 * INTR_GETMASKS	logic to get `imr', `isr', and initialize `bit'
 * INTR_HANDLER(bit)	logic to access intrhand array head for `bit'
 * INTR_IMASK(ipl)	logic to access imask array for `ipl'
 * INTR_LOCAL_DECLS	local declarations (may be empty)
 * MASK_LOCAL_DECLS	local declarations (may be empty)
 * INTR_MASKPENDING	logic to mask `isr'
 * INTR_MASKRESTORE	logic to reset `imr'
 * INTR_MASKSIZE	size of interrupt mask in bits
 * INTR_SPURIOUS(bit)	print a spurious interrupt message for `bit'
 *
 * The following macros are optional:
 * INTR_HANDLER_SKIP(ih)	nonzero to skip intrhand invocation
 */

/*
 * Recompute interrupt masks.
 */
void
MASK_FUNCTIONNAME()
{
	int irq, level;
	struct intrhand *q;
	uint intrlevel[INTR_MASKSIZE];

	MASK_LOCAL_DECLS

	/* First, figure out which levels each IRQ uses. */
	for (irq = 0; irq < INTR_MASKSIZE; irq++) {
		uint levels = 0;
		for (q = INTR_HANDLER(irq); q != NULL; q = q->ih_next)
			levels |= 1 << q->ih_level;
		intrlevel[irq] = levels;
	}

	/*
	 * Then figure out which IRQs use each level.
	 * Note that we make sure never to overwrite imask[IPL_HIGH], in
	 * case an interrupt occurs during intr_disestablish() and causes
	 * an unfortunate splx() while we are here recomputing the masks.
	 */
	for (level = IPL_NONE; level < NIPLS; level++) {
		uint64_t irqs = 0;
		for (irq = 0; irq < INTR_MASKSIZE; irq++)
			if (intrlevel[irq] & (1 << level))
				irqs |= 1UL << irq;
		INTR_IMASK(level) = irqs;
	}

	/*
	 * There are tty, network and disk drivers that use free() at interrupt
	 * time, so vm > (tty | net | bio).
	 *
	 * Enforce a hierarchy that gives slow devices a better chance at not
	 * dropping data.
	 */
	INTR_IMASK(IPL_NET) |= INTR_IMASK(IPL_BIO);
	INTR_IMASK(IPL_TTY) |= INTR_IMASK(IPL_NET);
	INTR_IMASK(IPL_VM) |= INTR_IMASK(IPL_TTY);
	INTR_IMASK(IPL_CLOCK) |= INTR_IMASK(IPL_VM);
	INTR_IMASK(IPL_HIGH) |= INTR_IMASK(IPL_CLOCK);
	INTR_IMASK(IPL_IPI) |= INTR_IMASK(IPL_HIGH);

	/*
	 * These are pseudo-levels.
	 */
	INTR_IMASK(IPL_NONE) = 0;
}

/*
 * Interrupt dispatcher.
 */
uint32_t
INTR_FUNCTIONNAME(uint32_t hwpend, struct trap_frame *frame)
{
	struct cpu_info *ci = curcpu();
	uint64_t imr, isr, mask;
	int ipl;
	int bit;
	struct intrhand *ih;
	int rc, ret;
	INTR_LOCAL_DECLS

	INTR_GETMASKS;

	isr &= imr;
	if (isr == 0)
		return 0;	/* not for us */

	/*
	 * Mask all pending interrupts.
	 */
	INTR_MASKPENDING;

	/*
	 * If interrupts are spl-masked, mask them and wait for splx()
	 * to reenable them when necessary.
	 */
	if ((mask = isr & INTR_IMASK(frame->ipl)) != 0) {
		isr &= ~mask;
		imr &= ~mask;
	}

	/*
	 * Now process allowed interrupts.
	 */
	if (isr != 0) {
		int lvl, bitno;
		uint64_t tmpisr;

		__asm__ (".set noreorder\n");
		ipl = ci->ci_ipl;
		mips_sync();
		__asm__ (".set reorder\n");

		/* Service higher level interrupts first */
		for (lvl = NIPLS - 1; lvl != IPL_NONE; lvl--) {
			tmpisr = isr & (INTR_IMASK(lvl) ^ INTR_IMASK(lvl - 1));
			if (tmpisr == 0)
				continue;
			for (bitno = bit, mask = 1UL << bitno; mask != 0;
			    bitno--, mask >>= 1) {
				if ((tmpisr & mask) == 0)
					continue;

				rc = 0;
				for (ih = INTR_HANDLER(bitno); ih != NULL;
				    ih = ih->ih_next) {
#ifdef MULTIPROCESSOR
					register_t sr;
#endif
#if defined(INTR_HANDLER_SKIP)
					if (INTR_HANDLER_SKIP(ih) != 0)
						continue;
#endif
					splraise(ih->ih_level);
#ifdef MULTIPROCESSOR
					if (ih->ih_level < IPL_IPI) {
						sr = getsr();
						ENABLEIPI();
						if (ih->ih_level < IPL_CLOCK)
							__mp_lock(&kernel_lock);
					}
#endif
					ret = (*ih->ih_fun)(ih->ih_arg);
					if (ret != 0) {
						rc = 1;
						atomic_inc_long((unsigned long *)
						    &ih->ih_count.ec_count);
					}
#ifdef MULTIPROCESSOR
					if (ih->ih_level < IPL_IPI) {
						if (ih->ih_level < IPL_CLOCK)
							__mp_unlock(&kernel_lock);
						setsr(sr);
					}
#endif
					__asm__ (".set noreorder\n");
					ci->ci_ipl = ipl;
					mips_sync();
					__asm__ (".set reorder\n");
					if (ret == 1)
						break;
				}
				if (rc == 0)
					INTR_SPURIOUS(bitno);

				isr ^= mask;
				if ((tmpisr ^= mask) == 0)
					break;
			}
		}

		/*
		 * Reenable interrupts which have been serviced.
		 */
		INTR_MASKRESTORE;
	}

	return hwpend;
}

#undef	INTR_FUNCTIONNAME
#undef	MASK_FUNCTIONNAME
#undef	INTR_GETMASKS
#undef	INTR_HANDLER
#undef	INTR_HANDLER_SKIP
#undef	INTR_IMASK
#undef	INTR_LOCAL_DECLS
#undef	MASK_LOCAL_DECLS
#undef	INTR_MASKPENDING
#undef	INTR_MASKRESTORE
#undef	INTR_SPURIOUS

license: unknown


code:
/*	$OpenBSD: ip22.h,v 1.9 2014/07/17 19:51:58 miod Exp $	*/

/*
 * Copyright (c) 2012 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * IP20/IP22/IP24 definitions
 */

/* IP22/IP24 system types */

#define	IP22_INDY	0	/* IP24 Indy */
#define	IP22_CHALLS	1	/* IP24 Challenge S */
#define	IP22_INDIGO2	2	/* IP22 Indigo 2, Challenge M */

/* Interrupt handling priority */

#ifdef CPU_R8000
#define	INTPRI_BUSERR_TCC	(INTPRI_CLOCK + 1)
#define	INTPRI_BUSERR		(INTPRI_BUSERR_TCC + 1)
#else
#define	INTPRI_BUSERR		(INTPRI_CLOCK + 1)
#endif
#define	INTPRI_L1		(INTPRI_BUSERR + 1)
#define	INTPRI_L0		(INTPRI_L1 + 1)

extern int hpc_old;	/* nonzero if at least one HPC 1.x device found */
extern int bios_year;
extern int ip22_ecc;	/* nonzero if runinng with an ECC memory system */

int	ip22_fast_mode(void);
int	ip22_slow_mode(void);
int	ip22_restore_mode(int);

void	ip22_ConfigCache(struct cpu_info *);
extern void (*ip22_extsync)(struct cpu_info *, paddr_t, size_t, int);


code:
/*	$OpenBSD: ip22_machdep.c,v 1.20 2014/07/17 19:51:58 miod Exp $	*/

/*
 * Copyright (c) 2012 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Specific routines for IP20/22/24/26/28 systems. Yet another case of a
 * file which name no longer matches its original purpose.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/buf.h>
#include <sys/mount.h>

#include <uvm/uvm_extern.h>

#include <machine/autoconf.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/memconf.h>

#include <mips64/arcbios.h>
#include <mips64/archtype.h>

#include <sgi/sgi/ip22.h>
#include <sgi/localbus/imcreg.h>
#include <sgi/localbus/imcvar.h>
#include <sgi/hpc/hpcreg.h>
#include <sgi/hpc/iocreg.h>

#include "gio.h"
#include "tcc.h"

#if NGIO > 0
#include <sgi/gio/gioreg.h>
#include <sgi/gio/giovar.h>
#include <sgi/gio/lightreg.h>
#endif
#if NTCC > 0
#include <sgi/localbus/tccreg.h>
#include <sgi/localbus/tccvar.h>
#endif

extern char *hw_prod;

int	hpc_old = 0;
int	bios_year;
int	ip22_ecc = 0;

void	(*ip22_extsync)(struct cpu_info *, paddr_t, size_t, int);

void	ip22_arcbios_walk(void);
int	ip22_arcbios_walk_component(arc_config_t *);
void	ip22_cache_halt(int);
void	ip22_cache_sync(struct cpu_info *, paddr_t, size_t, int);
void	ip22_ecc_halt(int);
void	ip22_ecc_init(int);
void	ip22_memory_setup(void);
void	ip22_video_setup(void);

/*
 * Walk the ARCBios component tree to get hardware information we can't
 * obtain by other means.
 */

#define	IP22_HAS_L2		0x01
#define	IP22_HAS_AUDIO		0x02
#define	IP22_HAS_ENOUGH_FB	0x04
static int ip22_arcwalk_results = 0;

#if NGIO > 0
static char ip22_fb_names[GIO_MAX_FB][64];
#endif

int
ip22_arcbios_walk_component(arc_config_t *cf)
{
	struct cpu_info *ci = curcpu();
	arc_config_t *child;
	arc_config64_t *cf64 = (arc_config64_t *)cf;
#if NGIO > 0
	static int fbidx = 0;
#endif

	/*
	 * Split secondary caches are not supported.
	 * No IP22 processor module uses them anyway.
	 */
	if (cf->class == arc_CacheClass && cf->type == arc_SecondaryCache) {
		uint64_t key;

		if (bios_is_32bit)
			key = cf->key;
		else
			key = cf64->key >> 32;

		/*
		 * Secondary cache information is encoded as WWLLSSSS, where
		 * WW is the number of ways
		 *   (should be 01)
		 * LL is Log2(line size)
		 *   (should be 04 or 05 for IP20/IP22/IP24, 07 for IP26)
		 * SS is Log2(cache size in 4KB units)
		 *   (should be between 0007 and 0009)
		 */
		ci->ci_l2.size = (1 << 12) << (key & 0x0000ffff);
		ci->ci_l2.linesize = 1 << ((key >> 16) & 0xff);
		ci->ci_l2.sets = (key >> 24) & 0xff;
		ci->ci_l2.setsize = ci->ci_l2.size / ci->ci_l2.sets;

		ip22_arcwalk_results |= IP22_HAS_L2;
	}

	if (cf->class == arc_ControllerClass &&
	    cf->type == arc_AudioController) {
		ip22_arcwalk_results |= IP22_HAS_AUDIO;
	}

#if NGIO > 0
	if (cf->class == arc_ControllerClass &&
	    cf->type == arc_DisplayController) {
		if (fbidx >= GIO_MAX_FB) {
			/*
			 * Not worth printing a message. If the system is
			 * configured for glass console, it will get
			 * overwritten anyway.
			 */
			ip22_arcwalk_results |= IP22_HAS_ENOUGH_FB;
		} else {
			const char *id;
			size_t idlen;

			if (bios_is_32bit) {
				idlen = cf->id_len;
				id = (const char *)(vaddr_t)cf->id;
			} else {
				idlen = cf64->id_len;
				id = (const char *)cf64->id;
			}
			if (idlen != 0) {
				/* skip leading spaces */
				while (idlen > 0 && id[0] == ' ') {
					id++;
					idlen--;
				}
				/* skip SGI- prefix */
				if (idlen >= 4 && strncmp(id, "SGI-", 4) == 0) {
					id += 4;
					idlen -= 4;
				}
				if (idlen >= sizeof(ip22_fb_names[0]))
					idlen = sizeof(ip22_fb_names[0]) - 1;
				bcopy(id, ip22_fb_names[fbidx], idlen);
			}
			giofb_names[fbidx] = ip22_fb_names[fbidx];
			fbidx++;
		}
	}
#endif

	if (ip22_arcwalk_results ==
	    (IP22_HAS_L2 | IP22_HAS_AUDIO | IP22_HAS_ENOUGH_FB))
		return 0;	/* abort walk */

	for (child = (arc_config_t *)Bios_GetChild(cf); child != NULL;
	    child = (arc_config_t *)Bios_GetPeer(child)) {
		if (ip22_arcbios_walk_component(child) == 0)
			return 0;
	}

	return 1;	/* continue walk */
}

void
ip22_arcbios_walk()
{
#if NGIO == 0
	ip22_arcwalk_results |= IP22_HAS_ENOUGH_FB;
#endif
	(void)ip22_arcbios_walk_component((arc_config_t *)Bios_GetChild(NULL));
}

/*
 * Parse memory controller settings.
 */

#define	IMC_NREGION	3

void
ip22_memory_setup()
{
	uint i, bank, shift;
	uint32_t memc0, memc1;
	uint32_t memc;
	paddr_t base[IMC_NREGION], size[IMC_NREGION], limit;
	paddr_t start0, end0, start1, end1;
	struct phys_mem_desc *mem;

	/*
	 * Figure out the top of memory, as reported by ARCBios.
	 */

	limit = 0;
	for (i = 0, mem = mem_layout; i < MAXMEMSEGS; i++, mem++) {
		if (mem->mem_last_page > limit)
			limit = mem->mem_last_page;
	}
	limit = ptoa(limit);

	/*
	 * Figure out where the memory controller has put memory.
	 */

	memc0 = imc_read(IMC_MEMCFG0);
	memc1 = imc_read(IMC_MEMCFG1);

	/* Revision D onwards uses larger units, to allow for more memory */
	if ((imc_read(IMC_SYSID) & IMC_SYSID_REVMASK) >= 5)
		shift = IMC_MEMC_LSHIFT_HUGE;
	else
		shift = IMC_MEMC_LSHIFT;

	for (bank = 0; bank < IMC_NREGION; bank++) {
		memc = (bank & 2) ? memc1 : memc0;
		if ((bank & 1) == 0)
			memc >>= IMC_MEMC_BANK_SHIFT;
		memc &= IMC_MEMC_BANK_MASK;

		if ((memc & IMC_MEMC_VALID) == 0) {
			base[bank] = size[bank] = 0;
			continue;
		}

		base[bank] = (memc & IMC_MEMC_ADDR_MASK) >> IMC_MEMC_ADDR_SHIFT;
		base[bank] <<= shift;

		size[bank] = (memc & IMC_MEMC_SIZE_MASK) >> IMC_MEMC_SIZE_SHIFT;
		size[bank]++;
		size[bank] <<= shift;
	}

	/*
	 * Perform sanity checks on the above data..
	 */

	/* memory should not start below 128MB */
	for (bank = 0; bank < IMC_NREGION; bank++)
		if (size[bank] != 0 && base[bank] < (1ULL << 27))
			goto dopanic;

	/* banks should not overlap */
	for (bank = 1; bank < IMC_NREGION; bank++) {
		if (size[bank] == 0)
			continue;
		start0 = base[bank];
		end0 = base[bank] + size[bank];
		for (i = 0; i < bank; i++) {
			if (size[i] == 0)
				continue;
			start1 = base[i];
			end1 = base[i] + size[i];
			if (end0 > start1 && start0 < end1)
				goto dopanic;
		}
	}

	/*
	 * Now register all the memory beyond what ARCBios stopped at.
	 */

	for (bank = 0; bank < IMC_NREGION; bank++) {
		if (size[bank] == 0)
			continue;

		start0 = base[bank];
		end0 = base[bank] + size[bank];
		if (end0 <= limit)
			continue;

		if (start0 < limit)
			start0 = limit;

		memrange_register(atop(start0), atop(end0), 0);
	}

	return;

dopanic:
	bios_printf("** UNEXPECTED MEMORY CONFIGURATION **\n");
	bios_printf("MEMC0 %08x MEMC1 %08x\n", memc0, memc1);
	bios_printf("Please contact <sgi@openbsd.org>\n"
	    "Halting system.\n");
	Bios_Halt();
	for (;;) ;
}

void
ip22_video_setup()
{
#if NGIO > 0
	/*
	 * According to Linux, the base address of the console device,
	 * if there is a glass console, can be obtained by invoking the
	 * 8th function pointer of the vendor-specific vector table.
	 *
	 * This function returns a pointer to a list of addresses (or
	 * whatever struct it is), which second field is the address we
	 * are looking for.
	 *
	 * However, the address does not point to the base address of the
	 * slot the frame buffer, but to some registers in it. While this
	 * might help identifying the actual frame buffer type, at the
	 * moment we are only interested in the base address.
	 */

	long (*get_gfxinfo)(void);
	vaddr_t fbaddr;
	paddr_t fbphys;

	if (bios_is_32bit) {
		int32_t *vec, *addr;

		vec = (int32_t *)(int64_t)(int32_t)ArcBiosBase32->vendor_vect;
		get_gfxinfo = (long (*)(void))(int64_t)vec[8];
		addr = (int32_t *)(int64_t)(*get_gfxinfo)();
		fbaddr = addr[1];
	} else {
		int64_t *vec, *addr;

		vec = (int64_t *)ArcBiosBase64->vendor_vect;
		get_gfxinfo = (long (*)(void))vec[8];
		addr = (int64_t *)(*get_gfxinfo)();
		fbaddr = addr[1];
	}

	if (fbaddr >= CKSEG1_BASE && fbaddr < CKSSEG_BASE)
		fbphys = CKSEG1_TO_PHYS(fbaddr);
	else if (IS_XKPHYS(fbaddr))
		fbphys = XKPHYS_TO_PHYS(fbaddr);
	else
		return;

	if (!IS_GIO_ADDRESS(fbphys))
		return;

	/*
	 * Try to convert the address to a slot base or, for light(4)
	 * frame buffers, a frame buffer base.
	 *
	 * Verified addresses:
	 * grtwo	slot + 0x00000000
	 * impact	slot + 0x00000000
	 * light	slot + 0x003f0000 (LIGHT_ADDR_0)
	 * newport	slot + 0x000f0000 (NEWPORT_REX3_OFFSET)
	 */

	/* light(4) only exists on IP20 */
	if (sys_config.system_type == SGI_IP20) {
		paddr_t tmp = fbphys & ~((paddr_t)LIGHT_SIZE - 1);
		if (tmp == LIGHT_ADDR_0 || tmp == LIGHT_ADDR_1) {
			giofb_consaddr = tmp;
			return;
		}
	}

	if (fbphys < GIO_ADDR_EXP0)
		giofb_consaddr = GIO_ADDR_GFX;
	else if (fbphys < GIO_ADDR_EXP1)
		giofb_consaddr = GIO_ADDR_EXP0;
	else
		giofb_consaddr = GIO_ADDR_EXP1;
#endif
}

void
ip22_setup()
{
	u_long cpuspeed;
	uint8_t ip22_sysid;

	/*
	 * Get CPU information.
	 */
	bootcpu_hwinfo.c0prid = cp0_get_prid();
	bootcpu_hwinfo.c1prid = cp1_get_prid();
	cpuspeed = bios_getenvint("cpufreq");
	if (sys_config.system_type == SGI_IP20)
		cpuspeed <<= 1;
	switch (sys_config.system_type) {
	default:
		if (cpuspeed < 100)
			cpuspeed = 100;		/* reasonable default */
		break;
	case SGI_IP26:
		if (cpuspeed < 70)
			cpuspeed = 75;		/* reasonable default */
		break;
	}
	bootcpu_hwinfo.clock = cpuspeed * 1000000;
	bootcpu_hwinfo.type = (bootcpu_hwinfo.c0prid >> 8) & 0xff;

	switch (sys_config.system_type) {
	case SGI_IP20:
		ip22_sysid = 0;
		break;
	default:
	case SGI_IP22:
	case SGI_IP26:
	case SGI_IP28:
		ip22_sysid = (uint8_t)*(volatile uint32_t *)
		    PHYS_TO_XKPHYS(HPC_BASE_ADDRESS_0 + IOC_BASE + IOC_SYSID,
		      CCA_NC);
		break;
	}

	/*
	 * Scan ARCBios component list for useful information (L2 cache
	 * configuration, audio device availability)
	 */
	ip22_arcbios_walk();

	/*
	 * Figure out what critter we are running on.
	 */
	switch (sys_config.system_type) {
	case SGI_IP20:
		if (ip22_arcwalk_results & IP22_HAS_AUDIO)
			hw_prod = "Indigo";
		else
			hw_prod = "VME Indigo";
		break;
	case SGI_IP22:
		if (ip22_sysid & 0x01) {
			sys_config.system_subtype = IP22_INDIGO2;
			hw_prod = "Indigo2";
		} else {
			if (ip22_arcwalk_results & IP22_HAS_AUDIO) {
				sys_config.system_subtype = IP22_INDY;
				hw_prod = "Indy";
			} else {
				sys_config.system_subtype = IP22_CHALLS;
				hw_prod = "Challenge S";
			}
		}
		break;
	case SGI_IP26:
		sys_config.system_subtype = IP22_INDIGO2;
		hw_prod = "POWER Indigo2 R8000";
		break;
	case SGI_IP28:
		sys_config.system_subtype = IP22_INDIGO2;
		hw_prod = "POWER Indigo2 R10000";
		break;
	}

	/*
	 * Figure out whether we are running on an Indigo2 system with the
	 * ECC board.
	 */

	switch (sys_config.system_type) {
	default:
		break;
	case SGI_IP26:
		/*
		 * According to IRIX <sys/IP22.h>, earlier IP26 systems
		 * have an incomplete ECC board, and thus run in parity
		 * mode.
		 */
		if (((ip22_sysid & IOC_SYSID_BOARDREV) >>
		     IOC_SYSID_BOARDREV_SHIFT) >=
		    (0x18 >> IOC_SYSID_BOARDREV_SHIFT))
			ip22_ecc = 1;
		break;
	case SGI_IP28:
		/* All IP28 systems use the ECC board */
		ip22_ecc = 1;
		break;
	}

	if (ip22_ecc) {
		ip22_ecc_init(sys_config.system_type);
		md_halt = ip22_ecc_halt;
	}

	/*
	 * Figure out how many TLB entries are available.
	 */
	switch (bootcpu_hwinfo.type) {
	default:
#if defined(CPU_R4000) || defined(CPU_R4600) || defined(CPU_R5000)
	case MIPS_R4000:
	case MIPS_R4600:
	case MIPS_R5000:
		bootcpu_hwinfo.tlbsize = 48;
		break;
#endif
#ifdef CPU_R8000
	case MIPS_R8000:
		bootcpu_hwinfo.tlbsize = 128 * 3;
		break;
#endif
#ifdef CPU_R10000
	case MIPS_R10000:
		bootcpu_hwinfo.tlbsize = 64;
		break;
#endif
	}

	/*
	 * Compute memory layout. ARCBios may not report all memory (on
	 * Indigo, it seems to only report up to 128MB, and on Indigo2,
	 * up to 256MB).
	 */
	ip22_memory_setup();

	/*
	 * Get glass console information, if necessary.
	 */
	ip22_video_setup();

	/*
	 * Register DMA-reachable memory constraints.
	 * hpc(4) revision 1 and 1.5 only use 28-bit address pointers, thus
	 * only 256MB are addressable; unfortunately, since physical memory
	 * starts at 128MB, this enforces a 128MB limit.
	 *
	 * The following logic is pessimistic, as IP24 (Indy) systems have
	 * a revision 3 hpc(4) onboard, but will accept older revisions in
	 * expansion boards.
	 */
	switch (sys_config.system_type) {
	default:
#if defined(TGT_INDY) || defined(TGT_INDIGO2)
		dma_constraint.ucr_low = 0;
		dma_constraint.ucr_high = (1UL << 32) - 1;
		if (sys_config.system_subtype == IP22_INDIGO2)
			break;
		/* FALLTHROUGH */
#endif
	case SGI_IP20:
		dma_constraint.ucr_low = 0;
		dma_constraint.ucr_high = (1UL << 28) - 1;
		break;
	}

	/*
	 * Get ARCBios' current time.
	 */
	bios_year = Bios_GetTime()->Year;

	_device_register = arcs_device_register;
}

void
ip22_post_autoconf()
{
	/*
	 * Clear any pending bus error caused by the device probes.
	 */
#if NTCC > 0
	if (sys_config.system_type == SGI_IP26)
		tcc_bus_reset();
#endif
	imc_bus_reset();

	/*
	 * Relax DMA-reachable memory constraints if no 28-bit hpc(4)
	 * device has attached.
	 */
	if (hpc_old == 0) {
		uint64_t dmapages_before, dmapages;

		dmapages_before = uvm_pagecount(&dma_constraint);
		dma_constraint.ucr_high = (1UL << 32) - 1;
		dmapages = uvm_pagecount(&dma_constraint);
		if (dmapages_before != dmapages) {
			bufadjust(bufcachepercent * dmapages / 100);
			bufhighpages = bufpages;
		}
	}

	if (ip22_ecc) {
		ip22_fast_mode();
	}
}

/*
 * ECC board specific routines
 */

#define ecc_write(o,v) \
	*(volatile uint64_t *)PHYS_TO_XKPHYS(ECC_BASE + (o), CCA_NC) = (v)

static __inline__ uint32_t ip22_ecc_map(void);
static __inline__ void ip22_ecc_unmap(uint32_t);

static int ip22_ecc_mode;	/* 0 if slow mode, 1 if fast mode */

static __inline__ uint32_t
ip22_ecc_map()
{
	register uint32_t omemc1, nmemc1;

	omemc1 = imc_read(IMC_MEMCFG1);
	nmemc1 = omemc1 & ~IMC_MEMC_BANK_MASK;
	nmemc1 |= IMC_MEMC_VALID | (ECC_BASE >> IMC_MEMC_LSHIFT_HUGE);
	imc_write(IMC_MEMCFG1, nmemc1);
	(void)imc_read(IMC_MEMCFG1);
	mips_sync();

	return omemc1;
}

static __inline__ void
ip22_ecc_unmap(uint32_t omemc1)
{
	imc_write(IMC_MEMCFG1, omemc1);
	(void)imc_read(IMC_MEMCFG1);
	mips_sync();
}

int
ip22_fast_mode()
{
	register uint32_t memc1;

	if (ip22_ecc_mode == 0) {
		memc1 = ip22_ecc_map();
		ecc_write(ECC_CTRL, ECC_CTRL_ENABLE);
		mips_sync();
		(void)imc_read(IMC_MEMCFG1);
		imc_write(IMC_CPU_MEMACC, imc_read(IMC_CPU_MEMACC) & ~2);
		ip22_ecc_unmap(memc1);
		ip22_ecc_mode = 1;
#if NTCC > 0
		/* if (sys_config.system_type == SGI_IP26) */
			tcc_prefetch_enable();
#endif
		return 0;
	}

	return 1;
}

int
ip22_slow_mode()
{
	register uint32_t memc1;

	if (ip22_ecc_mode != 0) {
#if NTCC > 0
		/* if (sys_config.system_type == SGI_IP26) */
			tcc_prefetch_disable();
#endif
		memc1 = ip22_ecc_map();
		imc_write(IMC_CPU_MEMACC, imc_read(IMC_CPU_MEMACC) | 2);
		ecc_write(ECC_CTRL, ECC_CTRL_DISABLE);
		mips_sync();
		(void)imc_read(IMC_MEMCFG1);
		ip22_ecc_unmap(memc1);
		ip22_ecc_mode = 0;
		return 1;
	}

	return 0;
}

int
ip22_restore_mode(int mode)
{
	return mode ? ip22_fast_mode() : ip22_slow_mode();
}

void
ip22_ecc_init(int system_type)
{
	uint32_t memc1;

	/* setup slow mode */
	memc1 = ip22_ecc_map();
	imc_write(IMC_CPU_MEMACC, imc_read(IMC_CPU_MEMACC) | 2);
	ecc_write(ECC_CTRL, ECC_CTRL_DISABLE);
	mips_sync();
	(void)imc_read(IMC_MEMCFG1);
	/* clear pending errors, if any */
	ecc_write(ECC_CTRL, ECC_CTRL_INT_CLR);
	mips_sync();
	(void)imc_read(IMC_MEMCFG1);

	if (system_type == SGI_IP28) {
		ecc_write(ECC_CTRL, ECC_CTRL_CHK_DISABLE); /* XXX for now */
		mips_sync();
		(void)imc_read(IMC_MEMCFG1);
	}

	ip22_ecc_unmap(memc1);
	ip22_ecc_mode = 0;
}

void
ip22_ecc_halt(int howto)
{
	ip22_slow_mode();
	arcbios_halt(howto);
}

#if (defined(TGT_INDY) || defined(TGT_INDIGO2)) && \
    (defined(CPU_R4600) || defined(CPU_R5000))

/*
 * Cache routines for the secondary cache found on R4600SC and R5000SC
 * systems.
 */

#include <mips64/cache.h>
CACHE_PROTOS(ip22)

#define	IP22_L2_LINE		32UL
#define	IP22_CACHE_TAG_ADDRESS	0x80000000UL

static inline void ip22_l2_disable(void)
{
	/* halfword write: disable entire cache */
	*(volatile uint16_t *)(PHYS_TO_XKPHYS(IP22_CACHE_TAG_ADDRESS, CCA_NC)) =
	    0;
}
static inline void ip22_l2_enable(void)
{
	/* byte write: enable entire cache */
	*(volatile uint8_t *)(PHYS_TO_XKPHYS(IP22_CACHE_TAG_ADDRESS, CCA_NC)) =
	    0;
}

void
ip22_cache_halt(int howto)
{
	ip22_l2_disable();
	arcbios_halt(howto);
}

void
ip22_ConfigCache(struct cpu_info *ci)
{
	struct cache_info l2;

	/*
	 * Note that we are relying upon machdep.c only invoking us if we
	 * are running on an R4600 or R5000 system.
	 */
	if ((ip22_arcwalk_results & IP22_HAS_L2) == 0) {
		Mips5k_ConfigCache(ci);
		return;
	}

	l2 = ci->ci_l2;

	Mips5k_ConfigCache(ci);

	if (l2.linesize != IP22_L2_LINE || l2.sets != 1) {
		/*
		 * This should not happen. Better not try and tame an
		 * unknown beast.
		 */
		return;
	}

	ci->ci_l2 = l2;

	ci->ci_SyncCache = ip22_SyncCache;
	ip22_extsync = ip22_cache_sync;

	md_halt = ip22_cache_halt;
	ip22_l2_enable();
}

void
ip22_SyncCache(struct cpu_info *ci)
{
	vaddr_t sva, eva;

	Mips5k_SyncCache(ci);

	sva = PHYS_TO_XKPHYS(IP22_CACHE_TAG_ADDRESS, CCA_NC);
	eva = sva + ci->ci_l2.size;

	while (sva < eva) {
		*(volatile uint32_t *)sva = 0;
		sva += IP22_L2_LINE;
	}
}

void
ip22_cache_sync(struct cpu_info *ci, paddr_t _pa, size_t _sz, int how)
{
	size_t sz;
	paddr_t pa, tagbase;

	switch (how) {
	default:
	case CACHE_SYNC_W:
		break;
	case CACHE_SYNC_X:
	case CACHE_SYNC_R:
		/* extend the range to integral cache lines */
		pa = _pa & ~(IP22_L2_LINE - 1);
		sz = ((_pa + _sz + IP22_L2_LINE - 1) & ~(IP22_L2_LINE - 1)) -
		    pa;

		pa &= ci->ci_l2.size - 1;
		tagbase = PHYS_TO_XKPHYS(IP22_CACHE_TAG_ADDRESS, CCA_NC);

		while (sz != 0) {
			/* word write: invalidate line */
			*(volatile uint32_t *)(tagbase | pa) = 0;

			pa += IP22_L2_LINE;
			pa &= ci->ci_l2.size - 1;
			sz -= IP22_L2_LINE;
		}
		break;
	}
}

#endif


code:
/*	$OpenBSD: ip27.h,v 1.4 2010/05/09 18:37:47 miod Exp $	*/

/*
 * Copyright (c) 2009 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Non-XBow related IP27 and IP35 definitions
 */

/* NMI register save areas */

#define	IP27_NMI_KREGS_BASE		0x11400
#define	IP27_NMI_KREGS_SIZE		0x200	/* per CPU */
#define	IP27_NMI_EFRAME_BASE		0x11800

#define	IP35_NMI_KREGS_BASE		0x9000
#define	IP35_NMI_KREGS_SIZE		0x400	/* per CPU */
#define	IP35_NMI_EFRAME_BASE		0xa000

/* IP27 system types */

#define	IP27_SN0	0x00
#define	IP27_SN00	0x01
#define	IP27_O200	IP27_SN00
#define	IP27_O2K	IP27_SN0
#define	IP27_UNKNOWN	0x02

/* IP35 Brick types */

#define	IP35_CBRICK	0x00
#define	IP35_O350	0x02
#define	IP35_FUEL	0x04
#define	IP35_O300	0x08

/*
 * Specific device assignment
 */

#define	IP27_O200_BRIDGE_WIDGET		8
#define	IP27_O2K_BRIDGE_WIDGET		15

#define	IP27_IOC_SLOTNO			2
#define	IP27_IOC2_SLOTNO		6

/*
 * IP27 configuration structure.  Used to tell Origin 200 and Origin 2000
 * apart.
 */

#define	IP27_CONFIG_OFFSET	0x60	/* relative to LBOOTBASE */
#define	IP27_CONFIG_MAGIC	0x69703237636f6e66LL	/* "ip27conf" */

struct ip27_config {
	volatile uint32_t	time_const;
	volatile uint32_t	r10k_sysad;
	volatile uint64_t	magic;
	volatile uint64_t	cpu_hz;
	volatile uint64_t	hub_hz;
	volatile uint64_t	rtc_hz;
	volatile uint32_t	ecc_enable;
	volatile uint32_t	fprom_cyc;
	volatile uint32_t	ip27_subtype;
	volatile uint32_t	cksum;
	volatile uint32_t	flash_count;
	volatile uint32_t	fprom_wr;
	volatile uint32_t	prom_ver;
	volatile uint32_t	prom_rev;
	volatile uint32_t	config_specific;
};


code:
/*	$OpenBSD: ip27_machdep.c,v 1.65 2014/11/25 19:08:42 miod Exp $	*/

/*
 * Copyright (c) 2008, 2009 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Origin 200 / Origin 2000 / Onyx 2 (IP27), as well as
 * Origin 300 / Onyx 300 / Origin 350 / Onyx 350 / Onyx 4 / Origin 3000 /
 * Fuel / Tezro (IP35) specific code.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/reboot.h>
#include <sys/tty.h>
#include <sys/atomic.h>

#include <mips64/arcbios.h>
#include <mips64/archtype.h>

#include <machine/autoconf.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <mips64/mips_cpu.h>
#include <machine/memconf.h>
#include <machine/mnode.h>

#include <uvm/uvm_extern.h>

#include <sgi/sgi/ip27.h>
#include <sgi/sgi/l1.h>
#include <sgi/xbow/hub.h>
#include <sgi/xbow/widget.h>
#include <sgi/xbow/xbow.h>

#include <sgi/pci/iofreg.h>
#include <dev/ic/comvar.h>

#include <dev/cons.h>

extern char *hw_prod;

extern void (*md_halt)(int);

paddr_t	ip27_widget_short(int16_t, u_int);
paddr_t	ip27_widget_long(int16_t, u_int);
paddr_t	ip27_widget_map(int16_t, u_int,bus_addr_t *, bus_size_t *);
int	ip27_widget_id(int16_t, u_int, uint32_t *);
int	ip27_widget_id_early(int16_t, u_int, uint32_t *);

void	ip27_halt(int);

unsigned int xbow_long_shift = 29;

static paddr_t io_base;
static int ip35 = 0;
uint	 maxnodes;
gda_t	*gda;

int	ip27_hub_intr_register(int, int, int *);
int	ip27_hub_intr_establish(int (*)(void *), void *, int, int,
	    const char *, struct intrhand *);
void	ip27_hub_intr_disestablish(int);
void	ip27_hub_intr_clear(int);
void	ip27_hub_intr_set(int);
uint32_t hubpi_intr0(uint32_t, struct trap_frame *);
uint32_t hubpi_intr1(uint32_t, struct trap_frame *);
void	ip27_hub_intr_makemasks0(void);
void	ip27_hub_intr_makemasks1(void);
void	ip27_hub_setintrmask(int);
void	ip27_hub_splx(int);

void	ip27_attach_node(struct device *, int16_t);
int	ip27_print(void *, const char *);
void	ip27_nmi(void *);

/*
 * IP27 interrupt handling declarations: 128 hw sources, plus timers and
 * hub error sources; 5 levels.
 */

struct intrhand *hubpi_intrhand0[HUBPI_NINTS];
struct intrhand *hubpi_intrhand1[HUBPI_NINTS];

#ifdef notyet
#define	INTPRI_XBOW_HUB		(INTPRI_CLOCK + 1)	/* HUB errors */
#define	INTPRI_XBOW_TIMER	(INTPRI_XBOW_HUB + 1)	/* prof timer */
#define	INTPRI_XBOW_CLOCK	(INTPRI_XBOW_TIMER + 1)	/* RTC */
#define	INTPRI_XBOW_HW1		(INTPRI_XBOW_CLOCK + 1)	/* HW level 1 */
#else
#define	INTPRI_XBOW_HW1		(INTPRI_CLOCK + 1)	/* HW level 1 */
#endif
#define	INTPRI_XBOW_HW0		(INTPRI_XBOW_HW1 + 1)	/* HW level 0 */

struct {
	uint64_t hw[2];
} hubpi_intem, hubpi_imask[NIPLS];

void
ip27_setup()
{
	struct ip27_config *ip27_config;
	uint64_t synergy0_0;
	console_t *cons;
	nmi_t *nmi;
	static char unknown_model[20];

	io_base = PHYS_TO_XKPHYS_UNCACHED(0, SP_IO);

	ip35 = sys_config.system_type == SGI_IP35;

	if (ip35) {
		/*
		 * Get brick model type.
		 * We need to access the Synergy registers through the remote
		 * HUB interface, local access is protected on some models.
		 * Synergy0 register #0 is a 16 bits identification register.
		 */
		synergy0_0 = IP27_RHSPEC_L(0, HSPEC_SYNERGY(0, 0));
		sys_config.system_subtype = (synergy0_0 & 0xf000) >> 12;
		switch (sys_config.system_subtype) {
		case IP35_O350:	/* Chimera */
			hw_prod = "Origin 350";
			break;
		case IP35_FUEL:	/* Asterix */
			hw_prod = "Fuel";
			break;
		case IP35_O300:	/* Speedo2 */
			hw_prod = "Origin 300";
			break;
		case IP35_CBRICK:
			/* regular C-Brick, must be an Origin 3000 system */
			hw_prod = "Origin 3000";
			break;
		default:
			snprintf(unknown_model, sizeof unknown_model,
			    "Unknown IP35 type %x", sys_config.system_subtype);
			hw_prod = unknown_model;
			break;
		}
	} else {
		ip27_config = (struct ip27_config *)
		    IP27_LHSPEC_ADDR(LBOOTBASE_IP27 + IP27_CONFIG_OFFSET);
		if (ip27_config->magic == IP27_CONFIG_MAGIC)
			sys_config.system_subtype = ip27_config->ip27_subtype;
		else
			sys_config.system_subtype = IP27_UNKNOWN;
		switch (sys_config.system_subtype) {
		case IP27_O2K:
			hw_prod = "Origin 2000";
			break;
		case IP27_O200:
			hw_prod = "Origin 200";
			break;
		default:
			snprintf(unknown_model, sizeof unknown_model,
			    "Unknown IP27 type %x", sys_config.system_subtype);
			hw_prod = unknown_model;
			break;
		}
	}

	/*
	 * Register DMA-reachable memory constraints.
	 * The xbridge(4) is limited to a 31-bit region (its IOMMU features
	 * are too restricted to be of use).
	 */
	dma_constraint.ucr_low = 0;
	dma_constraint.ucr_high = (1UL << 31) - 1;

	xbow_widget_base = ip27_widget_short;
	xbow_widget_map = ip27_widget_map;
	xbow_widget_id = ip27_widget_id_early;

	md_halt = ip27_halt;

	/*
	 * Figure out as early as possibly whether we are running in M
	 * or N mode.
	 */

	kl_init(ip35);
	if (kl_n_mode != 0)
		xbow_long_shift = 28;

	/*
	 * Initialize the early console parameters.
	 * This assumes it is either on IOC3 or IOC4, accessible through
	 * a widget small window.
	 *
	 * Since IOC3 and IOC4 use different clocks, we need to tell them
	 * apart early. We rely on the serial port offset within the IOC
	 * space.
	 */

	cons = kl_get_console();
	xbow_build_bus_space(&sys_config.console_io, 0,
	    8 /* whatever nonzero */);
	/* point to devio base */
	sys_config.console_io.bus_base =
	    cons->uart_base & 0xfffffffffff00000UL;
	comconsaddr = cons->uart_base & 0x00000000000fffffUL;
	comconsrate = cons->baud;
	if (comconsrate < 50 || comconsrate > 115200)
		comconsrate = 9600;
	if ((comconsaddr & 0xfff) < 0x380) {
		/* IOC3 */
		comconsfreq = 22000000 / 3;
	} else {
		/* IOC4 */
		uint32_t ioc4_mcr;
		paddr_t ioc4_base;

		/*
		 * IOC4 clocks are derived from the PCI clock,
		 * so we need to figure out whether this is an 66MHz
		 * or a 33MHz bus.
		 */
		ioc4_base = sys_config.console_io.bus_base;
		ioc4_mcr = *(volatile uint32_t *)(ioc4_base + IOC4_MCR);
		if (ioc4_mcr & IOC4_MCR_PCI_66MHZ)
			comconsfreq = 66666667;
		else
			comconsfreq = 33333333;
	}
	comconsiot = &sys_config.console_io;

	/*
	 * Force widget interrupts to run through us, unless a
	 * better interrupt master widget is found.
	 */

	xbow_intr_widget_intr_register = ip27_hub_intr_register;
	xbow_intr_widget_intr_establish = ip27_hub_intr_establish;
	xbow_intr_widget_intr_disestablish = ip27_hub_intr_disestablish;
	xbow_intr_widget_intr_clear = ip27_hub_intr_clear;
	xbow_intr_widget_intr_set = ip27_hub_intr_set;

	set_intr(INTPRI_XBOW_HW1, CR_INT_1, hubpi_intr1);
	set_intr(INTPRI_XBOW_HW0, CR_INT_0, hubpi_intr0);
	register_splx_handler(ip27_hub_splx);

	/*
	 * Disable all hardware interrupts.
	 */

	IP27_LHUB_S(HUBPI_CPU0_IMR0, 0);
	IP27_LHUB_S(HUBPI_CPU0_IMR1, 0);
	IP27_LHUB_S(HUBPI_CPU1_IMR0, 0);
	IP27_LHUB_S(HUBPI_CPU1_IMR1, 0);
	(void)IP27_LHUB_L(HUBPI_IR0);
	(void)IP27_LHUB_L(HUBPI_IR1);
	if (ip35) {
		IP27_RHUB_PI_S(masternasid, 1, HUBPI_CPU0_IMR0, 0);
		IP27_RHUB_PI_S(masternasid, 1, HUBPI_CPU0_IMR1, 0);
		IP27_RHUB_PI_S(masternasid, 1, HUBPI_CPU1_IMR0, 0);
		IP27_RHUB_PI_S(masternasid, 1, HUBPI_CPU1_IMR1, 0);
		(void)IP27_RHUB_PI_L(masternasid, 1, HUBPI_IR0);
		(void)IP27_RHUB_PI_L(masternasid, 1, HUBPI_IR1);
	}

	/*
	 * Setup NMI handler.
	 */
	nmi = IP27_NMI(0);
	nmi->magic = NMI_MAGIC;
	nmi->cb = (vaddr_t)ip27_nmi;
	nmi->cb_complement = ~nmi->cb;
	nmi->cb_arg = 0;

	/*
	 * Set up Node 0's HUB.
	 */
	IP27_LHUB_S(HUBPI_REGION_PRESENT, 0xffffffffffffffff);
	IP27_LHUB_S(HUBPI_CALIAS_SIZE, PI_CALIAS_SIZE_0);
	if (ip35) {
		IP27_RHUB_PI_S(masternasid, 1,
		    HUBPI_REGION_PRESENT, 0xffffffffffffffff);
		IP27_RHUB_PI_S(masternasid, 1,
		    HUBPI_CALIAS_SIZE, PI_CALIAS_SIZE_0);
	}

	/*
	 * Compute interrupt register address.
	 */
	xbow_intr_address = (1UL << 47) /* XIO I/O space */ |
	    (masternasid << (ip35 ? 39 : 38)) |
	    ((uint64_t)IP27_RHUB_ADDR(0, HUBPI_IR_CHANGE) -
	     IP27_NODE_IO_BASE(0)) /* HUB register offset */;

	_device_register = dksc_device_register;
}

/*
 * Autoconf enumeration
 */

void
ip27_autoconf(struct device *parent)
{
	union {
		struct mainbus_attach_args maa;
		struct cpu_attach_args caa;
	} u;
	uint node;

	xbow_widget_id = ip27_widget_id;

	/*
	 * Attach the CPU we are running on early; other processors,
	 * if any, will get attached as they are discovered.
	 */

	bzero(&u, sizeof u);
	u.maa.maa_name = "cpu";
	u.maa.maa_nasid = currentnasid = masternasid;
	u.caa.caa_hw = &bootcpu_hwinfo;
	config_found(parent, &u, ip27_print);
	u.maa.maa_name = "clock";
	config_found(parent, &u, ip27_print);

	/*
	 * Now attach all nodes' I/O devices.
	 */

	ip27_attach_node(parent, masternasid);
	for (node = 0; node < maxnodes; node++) {
		if (gda->nasid[node] < 0)
			continue;
		if (gda->nasid[node] == masternasid)
			continue;
		ip27_attach_node(parent, gda->nasid[node]);
	}
}

void
ip27_attach_node(struct device *parent, int16_t nasid)
{
	union {
		struct mainbus_attach_args maa;
		struct spdmem_attach_args saa;
	} u;
	uint dimm;
	void *match;

	currentnasid = nasid;
	bzero(&u, sizeof u);
	if (ip35) {
		l1_display(nasid, TRUE, "OpenBSD/sgi");

		u.maa.maa_name = "spdmem";
		u.maa.maa_nasid = nasid;
		for (dimm = 0; dimm < L1_SPD_DIMM_MAX; dimm++) {
			u.saa.dimm = dimm;
			/*
			 * inline config_found_sm() without printing a message
			 * if match() fails, to avoid getting
			 * ``spdmem not configured'' for empty memory slots.
			 */
			if ((match = config_search(NULL, parent, &u)) != NULL)
				config_attach(parent, match, &u, ip27_print);
		}
	}
	u.maa.maa_name = "xbow";
	u.maa.maa_nasid = nasid;
	config_found(parent, &u, ip27_print);
}

int
ip27_print(void *aux, const char *pnp)
{
	struct mainbus_attach_args *maa = aux;

	if (pnp != NULL)
		printf("%s at %s", maa->maa_name, pnp);
	printf(" nasid %d", maa->maa_nasid);

	return UNCONF;
}

/*
 * Widget mapping.
 */

paddr_t
ip27_widget_short(int16_t nasid, u_int widget)
{
	/*
	 * A hardware bug on the PI side of the Hub chip (at least in
	 * earlier versions) causes accesses to the short window #0
	 * to be unreliable.
	 * The PROM implements a workaround by remapping it to
	 * big window #6 (the last programmable big window).
	 */
	if (widget == 0)
		return ip27_widget_long(nasid, IOTTE_SWIN0);

	return ((uint64_t)(widget) << 24) |
	    ((uint64_t)(nasid) << kl_n_shift) | io_base;
}

paddr_t
ip27_widget_long(int16_t nasid, u_int window)
{
	return ((uint64_t)(window + 1) << xbow_long_shift) |
	    ((uint64_t)(nasid) << kl_n_shift) | io_base;
}

paddr_t
ip27_widget_map(int16_t nasid, u_int widget, bus_addr_t *offs, bus_size_t *len)
{
	uint tte, avail_tte;
	uint64_t iotte;
	paddr_t delta, start, end;
	int s;

	/*
	 * On Origin systems, we can only have partial views of the widget
	 * address space, due to the addressing scheme limiting each node's
	 * address space to 31 to 33 bits.
	 *
	 * The largest window is 256MB or 512MB large, depending on the
	 * mode the system is in (M/N).
	 */

	/*
	 * Round the requested range to a large window boundary.
	 */

	start = *offs;
	end = start + *len;

	start = (start >> xbow_long_shift);
	end = (end + (1 << xbow_long_shift) - 1) >> xbow_long_shift;

	/*
	 * See if an existing IOTTE covers part of the mapping we are asking
	 * for.  If so, reuse it and truncate the caller's range.
	 */

	s = splhigh();	/* XXX or disable interrupts completely? */

	avail_tte = IOTTE_MAX;
	for (tte = 0; tte < IOTTE_MAX; tte++) {
		if (tte == IOTTE_SWIN0)
			continue;

		iotte = IP27_RHUB_L(nasid, HUBIOBASE + HUBIO_IOTTE(tte));
		if (IOTTE_WIDGET(iotte) == 0) {
			if (avail_tte == IOTTE_MAX)
				avail_tte = tte;
			continue;
		}
		if (IOTTE_WIDGET(iotte) != widget)
			continue;

		if (IOTTE_OFFSET(iotte) < start ||
		    (IOTTE_OFFSET(iotte) + 1) >= end)
			continue;

		/*
		 * We found a matching IOTTE (an exact match if we asked for
		 * less than the large window size, a partial match otherwise).
		 * Reuse it (since we never unmap IOTTE at this point, there
		 * is no need to maintain a reference count).
		 */
		break;
	}

	/*
	 * If we found an unused IOTTE while searching above, program it
	 * to map the beginning of the requested range.
	 */

	if (tte == IOTTE_MAX && avail_tte != IOTTE_MAX) {
		tte = avail_tte;

		/* XXX I don't understand why it's not device space. */
		iotte = IOTTE(IOTTE_SPACE_MEMORY, widget, start);
		IP27_RHUB_S(nasid, HUBIOBASE + HUBIO_IOTTE(tte), iotte);
		(void)IP27_RHUB_L(nasid, HUBIOBASE + HUBIO_IOTTE(tte));
	}

	splx(s);

	if (tte != IOTTE_MAX) {
		delta = *offs - (start << xbow_long_shift);
		/* *offs unmodified */
		*len = (1 << xbow_long_shift) - delta;

		return ip27_widget_long(nasid, tte) + delta;
	}

	return 0UL;
}

/*
 * Widget enumeration
 */

int
ip27_widget_id(int16_t nasid, u_int widget, uint32_t *wid)
{
	paddr_t wpa;
	uint32_t id;

	if (widget != 0)
	{
		if (widget < WIDGET_MIN || widget > WIDGET_MAX)
			return EINVAL;
	}

	wpa = ip27_widget_short(nasid, widget);
	if (guarded_read_4(wpa + (WIDGET_ID | 4), &id) != 0)
		return ENXIO;

	if (wid != NULL)
		*wid = id;

	return 0;
}

/*
 * Same as the above, but usable before we can handle faults.
 * Expects the caller to only use valid widget numbers...
 */
int
ip27_widget_id_early(int16_t nasid, u_int widget, uint32_t *wid)
{
	paddr_t wpa;

	if (widget != 0)
	{
		if (widget < WIDGET_MIN || widget > WIDGET_MAX)
			return EINVAL;
	}

	wpa = ip27_widget_short(nasid, widget);
	if (wid != NULL)
		*wid = *(uint32_t *)(wpa + (WIDGET_ID | 4));

	return 0;
}

/*
 * Reboot code
 */

void
ip27_halt(int howto)
{
	uint32_t promop;
	uint node;
	uint64_t nibase, action;

	/*
	 * Even if ARCBios TLB and exception vectors are restored,
	 * returning to ARCBios doesn't work.
	 *
	 * So, instead, send a reset through the network interface
	 * of the Hub space.  Although there seems to be a way to tell
	 * the PROM which action we want it to take afterwards, it
	 * always reboots for me...
	 */

	if (howto & RB_HALT) {
#if 0
		if (howto & RB_POWERDOWN)
			promop = GDA_PROMOP_HALT;
		else
			promop = GDA_PROMOP_EIM;
#else
		if (howto & RB_POWERDOWN) {
			if (ip35) {
				l1_exec_command(masternasid, "* pwr d");
				delay(1000000);
				printf("Powerdown failed, "
				    "please switch off power manually.\n");
			} else {
				printf("Software powerdown not supported, "
				    "please switch off power manually.\n");
			}
			for (;;) ;
		} else {
			printf("System halted.\n"
			    "Press any key to restart\n");
			cngetc();
			promop = GDA_PROMOP_REBOOT;
		}
#endif
	} else
		promop = GDA_PROMOP_REBOOT;

	promop |= GDA_PROMOP_MAGIC | /* GDA_PROMOP_NO_DIAGS | */
	    GDA_PROMOP_NO_MEMINIT;

#if 0
	/*
	 * That's what one would expect, based on the gda layout...
	 */
	gda->promop = promop;
#else
	/*
	 * ...but the magic location is in a different castle.
	 * And it's not even the same between IP27 and IP35.
	 * Laugh, everyone! It's what SGI wants us to.
	 */
	if (ip35)
		IP27_LHUB_S(HUBLBBASE_IP35 + 0x8010, promop);
	else
		IP27_LHUB_S(HUBPIBASE + 0x418, promop);
#endif

	if (ip35) {
		nibase = HUBNIBASE_IP35;
		action = NI_RESET_LOCAL_IP35 | NI_RESET_ACTION_IP35;
	} else {
		nibase = HUBNIBASE_IP27;
		action = NI_RESET_LOCAL_IP27 | NI_RESET_ACTION_IP27;
	}

	/*
	 * Reset all other nodes, if present.
	 */

	for (node = 0; node < maxnodes; node++) {
		if (gda->nasid[node] < 0)
			continue;
		if (gda->nasid[node] == masternasid)
			continue;
		IP27_RHUB_S(gda->nasid[node],
		    nibase + HUBNI_RESET_ENABLE, NI_RESET_ENABLE);
		IP27_RHUB_S(gda->nasid[node],
		    nibase + HUBNI_RESET, action);
	}
	IP27_LHUB_S(nibase + HUBNI_RESET_ENABLE, NI_RESET_ENABLE);
	IP27_LHUB_S(nibase + HUBNI_RESET, action);
}

/*
 * Local HUB interrupt handling routines
 */

/*
 * Find a suitable interrupt bit for the given interrupt.
 */
int
ip27_hub_intr_register(int widget, int level, int *intrbit)
{
	int bit;

	/*
	 * Try to allocate a bit on hardware level 0 first.
	 */
	for (bit = HUBPI_INTR0_WIDGET_MAX; bit >= HUBPI_INTR0_WIDGET_MIN; bit--)
		if ((hubpi_intem.hw[0] & (1UL << bit)) == 0)
			goto found;

	/*
	 * If all level 0 sources are in use, try to allocate a bit on
	 * level 1.
	 */
	for (bit = HUBPI_INTR1_WIDGET_MAX; bit >= HUBPI_INTR1_WIDGET_MIN; bit--)
		if ((hubpi_intem.hw[1] & (1UL << bit)) == 0) {
			bit += HUBPI_NINTS;
			goto found;
		}

	return EINVAL;

found:
	*intrbit = bit;
	return 0;
}

/*
 * Register an interrupt handler for a given source, and enable it.
 */
int
ip27_hub_intr_establish(int (*func)(void *), void *arg, int intrbit,
    int level, const char *name, struct intrhand *ihstore)
{
	struct intrhand *ih, **anchor;
	int s;

#ifdef DIAGNOSTIC
	if (intrbit < 0 || intrbit >= HUBPI_NINTS + HUBPI_NINTS)
		return EINVAL;
#endif

	/*
	 * Widget interrupts are not supposed to be shared - the interrupt
	 * mask is supposedly large enough for all interrupt sources.
	 *
	 * XXX On systems with many widgets and/or nodes, this assumption
	 * XXX will no longer stand; we'll need to implement interrupt
	 * XXX sharing at some point.
	 */
	if (intrbit >= HUBPI_NINTS)
		anchor = &hubpi_intrhand1[intrbit % HUBPI_NINTS];
	else
		anchor = &hubpi_intrhand0[intrbit];
	if (*anchor != NULL)
		return EEXIST;

	if (ihstore == NULL) {
		ih = malloc(sizeof(*ih), M_DEVBUF, M_NOWAIT);
		if (ih == NULL)
			return ENOMEM;
		ih->ih_flags = IH_ALLOCATED;
	} else {
		ih = ihstore;
		ih->ih_flags = 0;
	}

	ih->ih_next = NULL;
	ih->ih_fun = func;
	ih->ih_arg = arg;
	ih->ih_level = level;
	ih->ih_irq = intrbit;
	if (name != NULL)
		evcount_attach(&ih->ih_count, name, &ih->ih_level);

	s = splhigh();

	*anchor = ih;

	hubpi_intem.hw[intrbit / HUBPI_NINTS] |= 1UL << (intrbit % HUBPI_NINTS);
	if (intrbit / HUBPI_NINTS != 0)
		ip27_hub_intr_makemasks1();
	else
		ip27_hub_intr_makemasks0();

	splx(s);	/* causes hw mask update */

	return 0;
}

void
ip27_hub_intr_disestablish(int intrbit)
{
	struct intrhand *ih, **anchor;
	int s;

#ifdef DIAGNOSTIC
	if (intrbit < 0 || intrbit >= HUBPI_NINTS + HUBPI_NINTS)
		return;
#endif

	if (intrbit >= HUBPI_NINTS)
		anchor = &hubpi_intrhand1[intrbit % HUBPI_NINTS];
	else
		anchor = &hubpi_intrhand0[intrbit];

	s = splhigh();

	if ((ih = *anchor) == NULL) {
		splx(s);
		return;
	}

	*anchor = NULL;

	hubpi_intem.hw[intrbit / HUBPI_NINTS] &=
	    ~(1UL << (intrbit % HUBPI_NINTS));
	if (intrbit / HUBPI_NINTS != 0)
		ip27_hub_intr_makemasks1();
	else
		ip27_hub_intr_makemasks0();

	splx(s);

	if (ISSET(ih->ih_flags, IH_ALLOCATED))
		free(ih, M_DEVBUF, 0);
}

void
ip27_hub_intr_clear(int intrbit)
{
	IP27_RHUB_PI_S(masternasid, 0, HUBPI_IR_CHANGE, PI_IR_CLR | intrbit);
}

void
ip27_hub_intr_set(int intrbit)
{
	IP27_RHUB_PI_S(masternasid, 0, HUBPI_IR_CHANGE, PI_IR_SET | intrbit);
}

void
ip27_hub_splx(int newipl)
{
	struct cpu_info *ci = curcpu();

	/* Update masks to new ipl. Order highly important! */
	__asm__ (".set noreorder\n");
	ci->ci_ipl = newipl;
	mips_sync();
	__asm__ (".set reorder\n");
	if (CPU_IS_PRIMARY(ci))
		ip27_hub_setintrmask(newipl);
	/* If we still have softints pending trigger processing. */
	if (ci->ci_softpending && newipl < IPL_SOFTINT)
		setsoftintr0();
}

/*
 * Level 0 and level 1 interrupt dispatchers.
 */

#define	INTR_FUNCTIONNAME	hubpi_intr0
#define	MASK_FUNCTIONNAME	ip27_hub_intr_makemasks0
#define	INTR_LOCAL_DECLS
#define	MASK_LOCAL_DECLS
#define	INTR_GETMASKS \
do { \
	/* XXX this assumes we run on cpu0 */ \
	isr = IP27_LHUB_L(HUBPI_IR0); \
	imr = IP27_LHUB_L(HUBPI_CPU0_IMR0); \
	bit = HUBPI_INTR0_WIDGET_MAX; \
} while (0)
#define	INTR_MASKPENDING \
do { \
	IP27_LHUB_S(HUBPI_CPU0_IMR0, imr & ~isr); \
	(void)IP27_LHUB_L(HUBPI_IR0); \
} while (0)
#define	INTR_IMASK(ipl)		hubpi_imask[ipl].hw[0]
#define	INTR_HANDLER(bit)	hubpi_intrhand0[bit]
#define	INTR_SPURIOUS(bit) \
do { \
	printf("spurious interrupt, source %d\n", bit); \
} while (0)
#define	INTR_MASKRESTORE \
do { \
	IP27_LHUB_S(HUBPI_CPU0_IMR0, imr); \
	(void)IP27_LHUB_L(HUBPI_IR0); \
} while (0)
#define	INTR_MASKSIZE	HUBPI_NINTS

#include <sgi/sgi/intr_template.c>

#define	INTR_FUNCTIONNAME	hubpi_intr1
#define	MASK_FUNCTIONNAME	ip27_hub_intr_makemasks1
#define	INTR_LOCAL_DECLS
#define	MASK_LOCAL_DECLS
#define	INTR_GETMASKS \
do { \
	/* XXX this assumes we run on cpu0 */ \
	isr = IP27_LHUB_L(HUBPI_IR1); \
	imr = IP27_LHUB_L(HUBPI_CPU0_IMR1); \
	bit = HUBPI_INTR1_WIDGET_MAX; \
} while (0)
#define	INTR_MASKPENDING \
do { \
	IP27_LHUB_S(HUBPI_CPU0_IMR1, imr & ~isr); \
	(void)IP27_LHUB_L(HUBPI_IR1); \
} while (0)
#define	INTR_IMASK(ipl)		hubpi_imask[ipl].hw[1]
#define	INTR_HANDLER(bit)	hubpi_intrhand1[bit]
#define	INTR_SPURIOUS(bit) \
do { \
	printf("spurious interrupt, source %d\n", bit + HUBPI_NINTS); \
} while (0)
#define	INTR_MASKRESTORE \
do { \
	IP27_LHUB_S(HUBPI_CPU0_IMR1, imr); \
	(void)IP27_LHUB_L(HUBPI_IR1); \
} while (0)
#define	INTR_MASKSIZE	HUBPI_NINTS

#include <sgi/sgi/intr_template.c>

void
ip27_hub_setintrmask(int level)
{
	/* XXX this assumes we run on cpu0 */
	IP27_LHUB_S(HUBPI_CPU0_IMR0,
	    hubpi_intem.hw[0] & ~hubpi_imask[level].hw[0]);
	(void)IP27_LHUB_L(HUBPI_IR0);
	IP27_LHUB_S(HUBPI_CPU0_IMR1,
	    hubpi_intem.hw[1] & ~hubpi_imask[level].hw[1]);
	(void)IP27_LHUB_L(HUBPI_IR1);
}

void
ip27_nmi(void *arg)
{
	vaddr_t regs_offs;
	register_t *regs, epc;
	struct trap_frame nmi_frame;
	extern int kdb_trap(int, struct trap_frame *);

	/*
	 * Build a ddb frame from the registers saved in the NMI KREGS
	 * area.
	 */

	if (ip35)
		regs_offs = IP35_NMI_KREGS_BASE;	/* XXX assumes cpu0 */
	else
		regs_offs = IP27_NMI_KREGS_BASE;	/* XXX assumes cpu0 */
	regs = IP27_UNCAC_ADDR(register_t *, 0, regs_offs);

	memset(&nmi_frame, 0xff, sizeof nmi_frame);

	/* general registers */
	memcpy(&nmi_frame.zero, regs, 32 * sizeof(register_t));
	regs += 32;
	nmi_frame.sr = *regs++;		/* COP_0_STATUS_REG */
	nmi_frame.cause = *regs++;	/* COP_0_CAUSE_REG */
	nmi_frame.pc = *regs++;
	nmi_frame.badvaddr = *regs++;	/* COP_0_BAD_VADDR */
	epc = *regs++;			/* COP_0_EXC_PC */
	regs++;				/* COP_0_CACHE_ERR */
	regs++;				/* NMI COP_0_STATUS_REG */

	setsr(getsr() & ~SR_BOOT_EXC_VEC);
	printf("NMI, PC = %p RA = %p SR = %08lx EPC = %p\n",
	    (void *)nmi_frame.pc, (void *)nmi_frame.ra, nmi_frame.sr,
	    (void *)epc);
#ifdef DDB
	(void)kdb_trap(-1, &nmi_frame);
#endif
	panic("NMI");
	/* NOTREACHED */
}


code:
/*	$OpenBSD: ip30.h,v 1.10 2012/06/17 12:34:19 miod Exp $	*/

/*
 * Copyright (c) 2008, 2009 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Physical memory on Octane starts at 512MB.
 *
 * This allows the small windows of all widgets to appear under physical
 * memory, and the Bridge window (#f) to sport the machine PROM at the
 * physical address where the CPU expects it on reset.
 */

#define	IP30_MEMORY_BASE		0x20000000
#define	IP30_MEMORY_ARCBIOS_LIMIT	0x40000000

/*
 * Specific widget assignment
 */

#define	IP30_HEART_WIDGET		8
#define	IP30_BRIDGE_WIDGET		15

#define	IP30_IOC_SLOTNO			2

/*
 * On-board IOC3 specific GPIO registers wiring
 */

/* Light bar control: 0 to dim, 1 to lit */
#define	IP30_GPIO_WHITE_LED		0	/* actually lightbulbs */
#define	IP30_GPIO_RED_LED		1
/* Classic Octane (1) vs Octane 2 (0), read only */
#define	IP30_GPIO_CLASSIC		2

/*
 * Flash PROM physical address, within BRIDGE widget
 */

#define	IP30_FLASH_BASE			0xc00000
#define	IP30_FLASH_SIZE			0x200000
#define	IP30_FLASH_ALT			0xe00000

/*
 * Multiprocessor configuration area
 */

#define	IP30_MAXCPUS		4

#define MPCONF_BASE		0x0000000000000600
#define MPCONF_LEN		0x80
#define	MPCONF_SHIFT		7	/* Log2(MPCONF_LEN) */

#define MPCONF_MAGIC(i)		((i) * MPCONF_LEN + 0x00)
#define MPCONF_PRID(i)		((i) * MPCONF_LEN + 0x04)
#define MPCONF_PHYSID(i)	((i) * MPCONF_LEN + 0x08)
#define MPCONF_VIRTID(i)	((i) * MPCONF_LEN + 0x0c)
#define MPCONF_SCACHESZ(i)	((i) * MPCONF_LEN + 0x10)
#define MPCONF_FANLOADS(i)	((i) * MPCONF_LEN + 0x14)
#define MPCONF_LAUNCH(i)	((i) * MPCONF_LEN + 0x18)
#define MPCONF_RNDVZ(i)		((i) * MPCONF_LEN + 0x20)
#define MPCONF_STACKADDR(i)	((i) * MPCONF_LEN + 0x40)
#define MPCONF_LPARAM(i)	((i) * MPCONF_LEN + 0x48)
#define MPCONF_RPARAM(i)	((i) * MPCONF_LEN + 0x50)
#define MPCONF_IDLEFLAG(i)	((i) * MPCONF_LEN + 0x58)

#define MPCONF_MAGIC_VAL	0xbaddeed2

/*
 * Global data area
 */

#define	GDA_BASE		0x0000000000000400

#define	GDA_MAGIC		0x58464552		/* XFER */

#if !defined(_LOCORE)
struct ip30_gda {
	uint32_t	magic;		/* GDA_MAGIC */
	uint32_t 	promop;
	void		(*nmi_cb)(void);
	uint64_t	masterspid;
	void		*tlb_handlers[3];
	uint64_t	nmi_count;
};

int	ip30_find_video(void);
#endif


code:
/*	$OpenBSD: ip30_machdep.c,v 1.63 2014/12/06 23:20:17 krw Exp $	*/

/*
 * Copyright (c) 2008, 2009 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * Octane (IP30) specific code.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/device.h>
#include <sys/tty.h>

#include <mips64/arcbios.h>
#include <mips64/cache.h>

#include <machine/autoconf.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <mips64/mips_cpu.h>
#include <machine/memconf.h>

#include <uvm/uvm_extern.h>

#include <sgi/sgi/ip30.h>
#include <sgi/xbow/widget.h>
#include <sgi/xbow/xbow.h>
#include <sgi/xbow/xbowdevs.h>
#include <sgi/xbow/xbridgereg.h>	/* BRIDGE_PCI0_MEM_SPACE_BASE */

#include <sgi/xbow/xheartreg.h>
#include <sgi/pci/iocreg.h>

#include <dev/ic/comvar.h>

extern char *hw_prod;

extern int	mbprint(void *, const char *);

#ifdef MULTIPROCESSOR
extern int      xheart_intr_establish(int (*)(void *), void *, int, int,
    const char *, struct intrhand *);
extern void     xheart_intr_set(int);
extern void     xheart_intr_clear(int);
extern void	xheart_setintrmask(int);

extern struct	user *proc0paddr;
#endif

uint32_t ip30_lights_frob(uint32_t, struct trap_frame *);
paddr_t	ip30_widget_short(int16_t, u_int);
paddr_t	ip30_widget_long(int16_t, u_int);
paddr_t	ip30_widget_map(int16_t, u_int, bus_addr_t *, bus_size_t *);
int	ip30_widget_id(int16_t, u_int, uint32_t *);
static u_long ip30_get_ncpusfound(void);

#ifdef DDB
void	ip30_nmi(void);			/* ip30_nmi.S */
void	ip30_nmi_handler(void);
#endif

static	paddr_t ip30_iocbase;

static const paddr_t mpconf =
    PHYS_TO_XKPHYS(MPCONF_BASE, CCA_COHERENT_EXCLWRITE);

static int ip30_cpu_exists(int);

void
ip30_setup()
{
	paddr_t heart;
	int bank;
	uint32_t memcfg;
	uint64_t start, count, end;
	u_long cpuspeed;
#ifdef DDB
	struct ip30_gda *gda;
#endif

	/*
	 * Scan for memory. ARCBios reports up to 1GB of memory as available,
	 * and anything after is reported as reserved.
	 */
	heart = PHYS_TO_XKPHYS(HEART_PIU_BASE, CCA_NC);
	for (bank = 0; bank < 8; bank++) {
		memcfg = *(volatile uint32_t *)
		    (heart + HEART_MEMORY_STATUS + bank * sizeof(uint32_t));
#ifdef DEBUG
		bios_printf("memory bank %d: %08x\n", bank, memcfg);
#endif

		if (!ISSET(memcfg, HEART_MEMORY_VALID))
			continue;

		count = ((memcfg & HEART_MEMORY_SIZE_MASK) >>
		    HEART_MEMORY_SIZE_SHIFT) + 1;
		start = (memcfg & HEART_MEMORY_ADDR_MASK) >>
		    HEART_MEMORY_ADDR_SHIFT;

		count <<= HEART_MEMORY_UNIT_SHIFT;
		start <<= HEART_MEMORY_UNIT_SHIFT;

		/* Physical memory starts at 512MB */
		start += IP30_MEMORY_BASE;
		end = start + count;
#ifdef DEBUG
		bios_printf("memory from %p to %p\n",
		    start, end);
#endif

		/*
		 * Add memory not obtained through ARCBios.
		 */
		if (start < IP30_MEMORY_BASE + IP30_MEMORY_ARCBIOS_LIMIT)
			start = IP30_MEMORY_BASE + IP30_MEMORY_ARCBIOS_LIMIT;
#if 0
		/*
		 * XXX Temporarily restrict memory to 1.5GB, until the bug
		 * XXX causing low memory corruption is found.
		 */
		if (end > 0x80000000UL)
			end = 0x80000000UL;
#endif
		if (start < end)
			memrange_register(atop(start), atop(end), 0);
	}

	/*
	 * Register DMA-reachable memory constraints.
	 * The xbridge(4) is limited to a 31-bit region (its IOMMU features
	 * are too restricted to be of use).
	 */
	dma_constraint.ucr_low = 0;
	dma_constraint.ucr_high = (1UL << 31) - 1;

	xbow_widget_base = ip30_widget_short;
	xbow_widget_map = ip30_widget_map;
	xbow_widget_id = ip30_widget_id;

	bootcpu_hwinfo.c0prid = cp0_get_prid();
	bootcpu_hwinfo.c1prid = cp1_get_prid();
	cpuspeed = bios_getenvint("cpufreq");
	if (cpuspeed < 100)
		cpuspeed = 175;		/* reasonable default */
	bootcpu_hwinfo.clock = cpuspeed * 1000000;
	bootcpu_hwinfo.tlbsize = 64;	/* R10000 family */
	bootcpu_hwinfo.type = (bootcpu_hwinfo.c0prid >> 8) & 0xff;

	/*
	 * Initialize the early console parameters.
	 * On Octane, the BRIDGE is always widget 15, and IOC3 is always
	 * mapped in memory space at address 0x500000.
	 *
	 * Also, note that by using a direct widget bus_space, there is
	 * no endianness conversion done on the bus addresses. Which is
	 * exactly what we need, since the IOC3 doesn't need any. Some
	 * may consider this an evil abuse of bus_space knowledge, though.
	 */

	xbow_build_bus_space(&sys_config.console_io, 0, IP30_BRIDGE_WIDGET);
	sys_config.console_io.bus_base =
	    ip30_widget_long(0, IP30_BRIDGE_WIDGET) +
	    BRIDGE_PCI0_MEM_SPACE_BASE + 0x500000;

	comconsaddr = IOC3_UARTA_BASE;
	comconsfreq = 22000000 / 3;
	comconsiot = &sys_config.console_io;
	comconsrate = bios_consrate;

#ifdef DDB
	/*
	 * Setup NMI handler.
	 */
	gda = (struct ip30_gda *)PHYS_TO_XKPHYS(GDA_BASE, CCA_CACHED);
	if (gda->magic == GDA_MAGIC)
		gda->nmi_cb = ip30_nmi;
#endif

	/*
	 * Octane and Octane2 can be told apart with a GPIO source bit
	 * in the onboard IOC3.
	 */
	ip30_iocbase = sys_config.console_io.bus_base;
	if (*(volatile uint32_t *)
	    (ip30_iocbase + IOC3_GPPR(IP30_GPIO_CLASSIC)) != 0)
		hw_prod = "Octane";
	else
		hw_prod = "Octane2";

	ncpusfound = ip30_get_ncpusfound();

	_device_register = arcs_device_register;
}

/*
 * Autoconf enumeration
 */

void
ip30_autoconf(struct device *parent)
{
	struct cpu_attach_args caa;
#ifdef MULTIPROCESSOR
	struct cpu_hwinfo hw;
	int cpuid;
#endif

	bzero(&caa, sizeof caa);
	caa.caa_maa.maa_nasid = masternasid;
	caa.caa_maa.maa_name = "cpu";
	caa.caa_hw = &bootcpu_hwinfo;
	config_found(parent, &caa, mbprint);

#ifdef MULTIPROCESSOR
	for (cpuid = 1; cpuid < IP30_MAXCPUS; cpuid++)
		if (ip30_cpu_exists(cpuid)) {
			/*
			 * Attach other processors with the same hardware
			 * information as the boot processor, unless we
			 * can get this information from the MPCONF area;
			 * since Octane processors should be identical
			 * (model, speed and cache), this should be safe.
			 */
			bcopy(&bootcpu_hwinfo, &hw, sizeof(struct cpu_hwinfo));
			hw.c0prid =
			    *(volatile uint32_t *)(mpconf + MPCONF_PRID(cpuid));
			hw.type = (hw.c0prid >> 8) & 0xff;
			hw.l2size = 1 << *(volatile uint32_t *)
			    (mpconf + MPCONF_SCACHESZ(cpuid));
			caa.caa_hw = &hw;
			config_found(parent, &caa, mbprint);
		}
#endif

	caa.caa_maa.maa_name = "clock";
	config_found(parent, &caa.caa_maa, mbprint);
	caa.caa_maa.maa_name = "xbow";
	config_found(parent, &caa.caa_maa, mbprint);
	caa.caa_maa.maa_name = "power";
	config_found(parent, &caa.caa_maa, mbprint);
}

/*
 * Widget mapping. IP30 only has one processor board node, so the nasid
 * parameter is ignored.
 */

paddr_t
ip30_widget_short(int16_t nasid, u_int widget)
{
	return PHYS_TO_XKPHYS(((uint64_t)widget << 24) | (1ULL << 28), CCA_NC);
}

paddr_t
ip30_widget_long(int16_t nasid, u_int widget)
{
	return PHYS_TO_XKPHYS((uint64_t)(widget) << 36, CCA_NC);
}

paddr_t
ip30_widget_map(int16_t nasid, u_int widget, bus_addr_t *offs, bus_size_t *len)
{
	paddr_t base;

	/*
	 * On Octane, the whole widget space is always accessible.
	 */

	base = ip30_widget_long(nasid, widget);
	*len = (1ULL << 36) - *offs;

	return base + *offs;
}

/*
 * Widget enumeration
 */

int
ip30_widget_id(int16_t nasid, u_int widget, uint32_t *wid)
{
	paddr_t linkpa, wpa;

	if (widget != 0) {
		if (widget < WIDGET_MIN || widget > WIDGET_MAX)
			return EINVAL;

		linkpa = ip30_widget_short(nasid, 0) + XBOW_WIDGET_LINK(widget);
		if (!ISSET(*(uint32_t *)(linkpa + (WIDGET_LINK_STATUS | 4)),
		    WIDGET_STATUS_ALIVE))
			return ENXIO;	/* not connected */
	}

	wpa = ip30_widget_short(nasid, widget);
	if (wid != NULL)
		*wid = *(uint32_t *)(wpa + (WIDGET_ID | 4));

	return 0;
}

/*
 * Figure out which video widget to use.
 *
 * If we are running with glass console, ConsoleOut will be `video(#)' with
 * the optional number being the number of the video device in the ARCBios
 * component tree.
 *
 * Unfortunately, we do not know how to match an ARCBios component to a
 * given widget (the PROM can... it's just not sharing this with us).
 *
 * So simply walk the available widget space and count video devices.
 */

int
ip30_find_video()
{
	uint widid, head;
	uint32_t id, vendor, product;
	char *p;

	if (strncmp(bios_console, "video", 5) != 0)
		return 0;	/* not graphics console */

	p = bios_console + 5;
	switch (*p) {
	case '(':
		/* 8 widgets max -> single digit */
		p++;
		if (*p == ')')
			head = 0;
		else {
			if (*p < '0' || *p > '9')
				return 0;
			head = *p++ - '0';
			if (*p != ')')
				return 0;
		}
		break;
	case '\0':
		head = 0;
		break;
	default:
		return 0;
	}

	for (widid = WIDGET_MAX; widid >= WIDGET_MIN; widid--) {
		if (ip30_widget_id(0, widid, &id) != 0)
			continue;

		vendor = WIDGET_ID_VENDOR(id);
		product = WIDGET_ID_PRODUCT(id);

		if ((vendor == XBOW_VENDOR_SGI2 &&
		    product == XBOW_PRODUCT_SGI2_ODYSSEY) ||
		    (vendor == XBOW_VENDOR_SGI5 &&
		    product == XBOW_PRODUCT_SGI5_IMPACT) ||
		    (vendor == XBOW_VENDOR_SGI5 &&
		    product == XBOW_PRODUCT_SGI5_KONA)) {
			/* found a video device */
			if (head == 0)
				return widid;
			head--;
		}
	}

	return 0;
}

/*
 * Fun with the lightbar
 */
uint32_t
ip30_lights_frob(uint32_t hwpend, struct trap_frame *cf)
{
	uint32_t gpioold, gpio;

	/* Light bar status: idle - white, user - red, system - both */

	gpio = gpioold = *(volatile uint32_t *)(ip30_iocbase + IOC3_GPDR);
	gpio &= ~((1 << IP30_GPIO_WHITE_LED) | (1 << IP30_GPIO_RED_LED));

	if (cf->sr & SR_KSU_USER)
		gpio |= (1 << IP30_GPIO_RED_LED);
	else {
		gpio |= (1 << IP30_GPIO_WHITE_LED);

		/* XXX SMP check other CPU is unidle */
		if (curproc != curcpu()->ci_schedstate.spc_idleproc)
			gpio |= (1 << IP30_GPIO_RED_LED);
	}

	if (gpio != gpioold)
		*(volatile uint32_t *)(ip30_iocbase + IOC3_GPDR) = gpio;

	return 0;	/* Real clock int handler will claim the interrupt. */
}

static int
ip30_cpu_exists(int cpuid)
{
	uint32_t magic =
	    *(volatile uint32_t *)(mpconf + MPCONF_MAGIC(cpuid));
	return magic == MPCONF_MAGIC_VAL;
}

u_long
ip30_get_ncpusfound(void)
{
	int i;
	int ncpus = 0;

	for (i = 0; i < IP30_MAXCPUS; i++)
		if (ip30_cpu_exists(i))
			ncpus++;

	return ncpus;
}

#ifdef DDB
void
ip30_nmi_handler()
{
	extern int kdb_trap(int, struct trap_frame *);
	extern void stacktrace(struct trap_frame *);
	struct trap_frame *fr0;
	int s;
#ifdef MULTIPROCESSOR
	struct trap_frame *fr1;
	struct cpu_info *ci = curcpu();
#endif

	setsr(getsr() & ~SR_BOOT_EXC_VEC);

	s = splhigh();
#ifdef MULTIPROCESSOR
	ENABLEIPI();

	if (!CPU_IS_PRIMARY(ci)) {
		for (;;) ;
	}
#endif

	printf("NMI\n");

	fr0 = (struct trap_frame *)PHYS_TO_XKPHYS(IP30_MEMORY_BASE + 0x4000,
	    CCA_CACHED);
#ifdef MULTIPROCESSOR
	fr1 = (struct trap_frame *)PHYS_TO_XKPHYS(IP30_MEMORY_BASE + 0x6000,
	    CCA_CACHED);
#endif

#ifdef MULTIPROCESSOR
	printf("cpu #0 traceback\n");
#endif
	stacktrace(fr0);
#ifdef MULTIPROCESSOR
	printf("cpu #1 traceback\n");
	stacktrace(fr1);
#endif

	kdb_trap(-1, fr0);

	splx(s);
	panic("NMI");
	/* NOTREACHED */
}
#endif

#ifdef MULTIPROCESSOR
void
hw_cpu_boot_secondary(struct cpu_info *ci)
{
	int cpuid =  ci->ci_cpuid;
	vaddr_t kstack;

#ifdef DEBUG
	uint64_t stackaddr =
		*(volatile uint64_t *)(mpconf + MPCONF_STACKADDR(cpuid));
	uint64_t lparam =
		*(volatile uint64_t *)(mpconf + MPCONF_LPARAM(cpuid));
	uint64_t launch =
		*(volatile uint64_t *)(mpconf + MPCONF_LAUNCH(cpuid));
	uint32_t magic =
		*(volatile uint32_t *)(mpconf + MPCONF_MAGIC(cpuid));
	uint32_t prid =
		*(volatile uint32_t *)(mpconf + MPCONF_PRID(cpuid));
	uint32_t physid =
		*(volatile uint32_t *)(mpconf + MPCONF_PHYSID(cpuid));
	uint32_t virtid =
		*(volatile uint32_t *)(mpconf + MPCONF_VIRTID(cpuid));
	uint32_t scachesz =
		*(volatile uint32_t *)(mpconf + MPCONF_SCACHESZ(cpuid));
	uint16_t fanloads =
		*(volatile uint16_t *)(mpconf + MPCONF_FANLOADS(cpuid));
	uint64_t rndvz =
		*(volatile uint64_t *)(mpconf + MPCONF_RNDVZ(cpuid));
	uint64_t rparam =
		*(volatile uint64_t *)(mpconf + MPCONF_RPARAM(cpuid));
	uint32_t idleflag =
		*(volatile uint32_t *)(mpconf + MPCONF_IDLEFLAG(cpuid));

	printf("ci:%p cpuid:%d magic:%x prid:%x physid:%x virtid:%x\n"
	    "scachesz:%u fanloads:%x launch:%llx rndvz:%llx\n"
	    "stackaddr:%llx lparam:%llx rparam:%llx idleflag:%x\n",
	    ci, cpuid, magic, prid, physid, virtid,
	    scachesz, fanloads, launch, rndvz,
	    stackaddr, lparam, rparam, idleflag);
#endif
	kstack = alloc_contiguous_pages(USPACE);
	if (kstack == 0)
		panic("unable to allocate idle stack");
	ci->ci_curprocpaddr = (void *)kstack;

	*(volatile uint64_t *)(mpconf + MPCONF_STACKADDR(cpuid)) =
	    (uint64_t)(kstack + USPACE);
	*(volatile uint64_t *)(mpconf + MPCONF_LPARAM(cpuid)) =
	    (uint64_t)ci;
	*(volatile uint64_t *)(mpconf + MPCONF_LAUNCH(cpuid)) =
	    (uint64_t)hw_cpu_spinup_trampoline;

	while (!cpuset_isset(&cpus_running, ci))
		;
}

void
hw_cpu_hatch(struct cpu_info *ci)
{
	int s;

	/*
	 * Set curcpu address on this processor.
	 */
	setcurcpu(ci);

	/*
	 * Make sure we can access the extended address space.
	 * Note that r10k and later do not allow XUSEG accesses
	 * from kernel mode unless SR_UX is set.
	 */
	setsr(getsr() | SR_KX | SR_UX);

	tlb_init(64);
	tlb_set_pid(0);

	/*
	 * Turn off bootstrap exception vectors.
	 */
	setsr(getsr() & ~SR_BOOT_EXC_VEC);

	/*
	 * Clear out the I and D caches.
	 */
	Mips10k_ConfigCache(ci);
	Mips_SyncCache(ci);

	(*md_startclock)(ci);

	ncpus++;
	cpuset_add(&cpus_running, ci);

	mips64_ipi_init();
	xheart_setintrmask(0);

	spl0();
	(void)updateimask(0);

	SCHED_LOCK(s);
	cpu_switchto(NULL, sched_chooseproc());
}

int
hw_ipi_intr_establish(int (*func)(void *), u_long cpuid)
{
	return xheart_intr_establish(func, (void *)cpuid, HEART_ISR_IPI(cpuid),
	    IPL_IPI, NULL, &curcpu()->ci_ipiih);
};

void
hw_ipi_intr_set(u_long cpuid)
{
	xheart_intr_set(HEART_ISR_IPI(cpuid));
}

void
hw_ipi_intr_clear(u_long cpuid)
{
	xheart_intr_clear(HEART_ISR_IPI(cpuid));
}
#endif


code:
/*	$OpenBSD: ip30_nmi.S,v 1.6 2012/10/03 11:18:23 miod Exp $	*/

/*
 * Copyright (c) 2010 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <machine/asm.h>
#include <machine/cpu.h>
#include <mips64/mips_cpu.h>
#include <machine/regnum.h>
#include <machine/cpustate.h>

#define	HW_CPU_NUMBER_REG	0x900000000ff50000	/* HEART_PRID */

#include "assym.h"

	.set	mips3

/*
 * The NMI handler routine is shared accross all processors.
 *
 * When the NMI is triggered, we might be in the middle of an exception
 * handler, and relying upon k0 and k1.
 *
 * Unfortunately, since there is no way to know whether our stack is valid,
 * we will need these registers. Therefore NMI are fatal if they occur in
 * kernel mode... but we don't even try to resume from them, yet.
 */
	.globl	ip30_nmi
ip30_nmi:
	.set	noat
	sync

	LOAD_XKPHYS(k0, CCA_CACHED)
	LA	k1, IP30_MEMORY_BASE + 0x4000
	PTR_ADDU k0, k0, k1

	/*
	 * We use part of the low memory as stack and save area.
	 * This is safe since we reserved this area early.
	 * We give ourselves 8KB, minus the size of the frame, of stack.
	 */

	LA	k1, HW_CPU_NUMBER_REG
	PTR_L	k1, 0(k1)
	beqz	k1, 1f
	nop

	/* CPU #1 */
	PTR_ADDU k0, k0, 0x2000
1:
	/*
	 * Save state.
	 * k0 = frame base, k1 = cpu number
	 */

	SAVE_CPU(k0, 0)
	SAVE_CPU_SREG(k0, 0)
	.set	at

	PTR_ADDU sp, k0, 0x2000
	LA	gp, _gp

	/*
	 * Interrupts should be disabled. Just in case they aren't,
	 * enforce this (a1 still contains the value of cop0 SR).
	 */

	and	a1, a1, ~SR_INT_ENAB
	mtc0	a1, COP_0_STATUS_REG
	MTC0_SR_IE_HAZARD

	jal	ip30_nmi_handler		/* ip30_machdep.c */
	nop

9:
	sync
	b	9b
	nop


code:
/*	$OpenBSD: ip32_machdep.c,v 1.22 2014/02/08 22:20:15 miod Exp $ */

/*
 * Copyright (c) 2003-2004 Opsycon AB  (www.opsycon.se / www.opsycon.com)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/device.h>
#include <sys/extent.h>
#include <sys/tty.h>

#include <uvm/uvm_extern.h>

#include <machine/autoconf.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <mips64/mips_cpu.h>
#include <machine/memconf.h>

#include <mips64/arcbios.h>

#include <sgi/localbus/crimebus.h>
#include <sgi/localbus/macebus.h>
#include <sgi/localbus/macebusvar.h>

#include <dev/ic/comvar.h>

extern char *hw_prod;

void crime_configure_memory(void);

void
crime_configure_memory(void)
{
	volatile u_int64_t *bank_ctrl;
	paddr_t addr;
	psize_t size;
	u_int64_t ctrl0, ctrl;
	u_int64_t first_page, last_page;
	int bank;
#ifdef DEBUG
	int i;
#endif

	bank_ctrl =
	    (void *)PHYS_TO_CKSEG1(CRIMEBUS_BASE + CRIME_MEM_BANK0_CONTROL);
	for (bank = 0; bank < CRIME_MAX_BANKS; bank++) {
		ctrl = bank_ctrl[bank];
		addr = (ctrl & CRIME_MEM_BANK_ADDR) << 25;
		size = (ctrl & CRIME_MEM_BANK_128MB) ? 128 : 32;

		/*
		 * Empty banks are reported as duplicates of bank #0.
		 */
		if (bank == 0)
			ctrl0 = ctrl;
		else if (ctrl == ctrl0)
			continue;

#ifdef DEBUG
		bios_printf("crime: bank %d contains %ld MB at %p\n",
		    bank, size, addr);
#endif

		/*
		 * Do not report memory regions below 256MB, since ARCBIOS
		 * will do.
		 */
		if (addr < 256 * 1024 * 1024)
			continue;

		addr += CRIME_MEMORY_OFFSET;
		size *= 1024 * 1024;
		first_page = atop(addr);
		last_page = atop(addr + size);

		memrange_register(first_page, last_page, 0);
	}

#ifdef DEBUG
	for (i = 0; i < MAXMEMSEGS; i++)
		if (mem_layout[i].mem_last_page != 0)
			bios_printf("MEM %d, %p to %p\n", i,
			    ptoa(mem_layout[i].mem_first_page),
			    ptoa(mem_layout[i].mem_last_page));
#endif
}

void
ip32_setup()
{
	u_long cpuspeed;

	/*
	 * IP32 PROM version 4.18:
	 *	PROM Monitor (BE)
	 *	Tue Oct 22 10:58:00 PDT 2002
	 *	VERSION 4.18
	 *	O2 R5K/R7K/R10K/R12K
	 *	IRIX 6.5.x IP32prom IP32PROM-v4
	 * has incorrect function pointers for all ARCBios calls running in
	 * CKSEG1 (all the reboot/restart routines).
	 *
	 * We attempt to detect this and fix the function pointers here.
	 *
	 * XXX 4.16 and 4.17 might need similar fixes. 4.15 is sane.
	 */

	if ((vaddr_t)bios_halt == RESET_EXC_VEC + 0x161c &&
	    (vaddr_t)bios_powerdown == RESET_EXC_VEC + 0x1648 &&
	    (vaddr_t)bios_restart == RESET_EXC_VEC + 0x1674 &&
	    (vaddr_t)bios_reboot == RESET_EXC_VEC + 0x16a0 &&
	    (vaddr_t)bios_eim == RESET_EXC_VEC + 0x15f0 &&
	    *(uint32_t *)bios_halt == 0xafbf0014 &&
	    *(uint32_t *)bios_powerdown == 0xafbf0014 &&
	    *(uint32_t *)bios_restart == 0xafb1001c &&
	    *(uint32_t *)bios_eim == 0xafbf0014) {
		bios_halt = (void (*)(void))(RESET_EXC_VEC + 0x15bc);
		bios_powerdown = (void (*)(void))(RESET_EXC_VEC + 0x15e8);
		bios_restart = (void (*)(void))(RESET_EXC_VEC + 0x1614);
		bios_reboot = (void (*)(void))(RESET_EXC_VEC + 0x1640);
		bios_eim = (void (*)(void))(RESET_EXC_VEC + 0x1590);
	}

	crime_configure_memory();

	/* R12K O2s must run with DSD on. */
	switch ((cp0_get_prid() >> 8) & 0xff) {
	case MIPS_R12000:
		setsr(getsr() | SR_DSD);
		protosr |= SR_DSD;
		break;
	}

	bootcpu_hwinfo.c0prid = cp0_get_prid();
	bootcpu_hwinfo.c1prid = cp1_get_prid();
	cpuspeed = bios_getenvint("cpufreq");
	if (cpuspeed < 100)
		cpuspeed = 180;		/* reasonable default */
	bootcpu_hwinfo.clock = cpuspeed * 1000000;
	bootcpu_hwinfo.type = (bootcpu_hwinfo.c0prid >> 8) & 0xff;

	/*
	 * Figure out how many TLB are available.
	 */
	switch (bootcpu_hwinfo.type) {
#ifdef CPU_RM7000
	case MIPS_RM7000:
		/*
		 * Rev A (version >= 2) CPU's have 64 TLB entries.
		 *
		 * However, the last 16 are only enabled if one
		 * particular configuration bit (mode bit #24)
		 * is set on cpu reset, so check whether the
		 * extra TLB are really usable.
		 *
		 * If they are disabled, they are nevertheless
		 * writable, but random TLB insert operations
		 * will never use any of them. This can be
		 * checked by inserting dummy entries and check
		 * if any of the last 16 entries have been used.
		 *
		 * Of course, due to the way the random replacement
		 * works (hashing various parts of the TLB data,
		 * such as address bits and ASID), not all the
		 * available TLB will be used; we simply check
		 * the highest valid TLB entry we can find and
		 * see if it is in the upper 16 entries or not.
		 */
		bootcpu_hwinfo.tlbsize = 48;
		if (((bootcpu_hwinfo.c0prid >> 4) & 0x0f) >= 2) {
			struct tlb_entry te;
			int e, lastvalid;

			tlb_set_wired(0);
			tlb_flush(64);
			for (e = 0; e < 64 * 8; e++)
				tlb_update(XKSSEG_BASE + ptoa(2 * e),
				    pfn_to_pad(0) | PG_ROPAGE);
			lastvalid = 0;
			for (e = 0; e < 64; e++) {
				tlb_read(e, &te);
				if ((te.tlb_lo0 & PG_V) != 0)
					lastvalid = e;
			}
			tlb_flush(64);
			if (lastvalid >= 48)
				bootcpu_hwinfo.tlbsize = 64;
		}
		break;
#endif
#ifdef CPU_R10000
	case MIPS_R10000:
	case MIPS_R12000:
	case MIPS_R14000:
		bootcpu_hwinfo.tlbsize = 64;
		break;
#endif
	default:	/* R5000, RM52xx */
		bootcpu_hwinfo.tlbsize = 48;
		break;
	}

	/* Setup serial console if ARCS is telling us not to use video. */
	if (strncmp(bios_console, "video", 5) != 0) {
		comconsaddr = MACE_ISA_SER1_OFFS;
		comconsfreq = 1843200;
		comconsiot = &macebus_tag;
		comconsrate = bios_consrate;
	}

	/* not sure if there is a way to tell O2 and O2+ apart */
	hw_prod = "O2";

	_device_register = arcs_device_register;
}