/*
 * Copyright (c) 1991-1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)ddi_impl.c	1.85	99/10/22 SMI"

/*
 * Sun4m specific DDI implementation
 */
#include <sys/types.h>
#include <sys/autoconf.h>
#include <sys/avintr.h>
#include <sys/bootconf.h>
#include <sys/conf.h>
#include <sys/cpu.h>
#include <sys/cpuvar.h>
#include <sys/ethernet.h>
#include <sys/idprom.h>
#include <sys/instance.h>
#include <sys/kmem.h>
#include <sys/machsystm.h>
#include <sys/mmu.h>
#include <sys/promif.h>
#include <sys/prom_plat.h>
#include <sys/sunndi.h>
#include <sys/ndi_impldefs.h>
#include <sys/sysmacros.h>
#include <sys/systeminfo.h>
#include <sys/fpu/fpusystm.h>

/*
 * External Functions (should be in header files)
 */
extern uint_t process_aflt(caddr_t);
extern uint_t timeout_cei(caddr_t);

/*
 * DDI Boot Configuration
 */

/*
 * Favoured drivers of this implementation
 * architecture.  These drivers MUST be present for
 * the system to boot at all.
 */
char *impl_module_list[] = {
	"rootnex",
	"options",
	"dma",		/* For old-style scsi booting (on sun4m?) */
	"sad",		/* Referenced via init_tbl[] */
	(char *)0
};

/*
 * Since Gypsy/Voyager is eol'd there aren't any platform pm drivers
 */
char *platform_pm_module_list[] = {
	(char *)0
};

/*
 * List of drivers to do multi-threaded probe/attach
 *
 * None on sun4m, no need on desktop.
 */
char *default_mta_drivers = "";


/*
 * These strings passed to not_serviced in locore.s
 */
const char busname_ovec[] = "onboard ";
const char busname_svec[] = "SBus ";
const char busname_vec[] = "";

#if !defined(SAS) && !defined(MPSAS)

idprom_t idprom;

#endif	/* !SAS && !MPSAS */

/*
 * Configure the hardware on the system.
 * Called before the rootfs is mounted
 */
void
configure(void)
{
	major_t major;
	dev_info_t *dip;

	/* We better have released boot by this time! */

	ASSERT(!bootops);

	/*
	 * Determine if an FPU is attached
	 */

#ifndef	MPSAS	/* no fpu module yet in MPSAS */
	fpu_probe();
#endif
	if (!fpu_exists)
		printf("No FPU in configuration\n");

	/*
	 * This following line fixes bugid 1041296; we need to do a
	 * prom_nextnode(0) because this call ALSO patches the DMA+
	 * bug in Campus-B and Phoenix. The prom uncaches the traptable
	 * page as a side-effect of devr_next(0) (which prom_nextnode calls),
	 * so this *must* be executed early on. (XXX This is untrue for sun4m)
	 */
	(void) prom_nextnode((dnode_t)0);

	/*
	 * Initialize devices on the machine.
	 * Uses configuration tree built by the PROMs to determine what
	 * is present, and builds a tree of prototype dev_info nodes
	 * corresponding to the hardware which identified itself.
	 */
#if !defined(SAS) && !defined(MPSAS)
	/*
	 * Record that devinfos have been made for "rootnex."
	 */
	major = ddi_name_to_major("rootnex");
	devnamesp[major].dn_flags |= DN_DEVI_MADE;

	/*
	 * Set the name part of the address to make the root conform
	 * to canonical form 1.  (Eliminates special cases later).
	 */
	dip = ddi_root_node();
	if (impl_ddi_sunbus_initchild(dip) != DDI_SUCCESS)
		cmn_err(CE_PANIC, "Could not initialize root nexus");

#ifdef	DDI_PROP_DEBUG
	(void) ddi_prop_debug(1);	/* Enable property debugging */
#endif	DDI_PROP_DEBUG

#endif	/* !SAS && !MPSAS */

}


/*
 * We set the cpu type from the idprom, if we can.
 * Note that we just read out the contents of it, for the most part.
 */
void
setcputype(void)
{
	/*
	 * We cache the idprom info early on so that we don't
	 * rummage thru the NVRAM unnecessarily later.
	 */
	(void) prom_getidprom((caddr_t)&idprom, sizeof (idprom));
}

/*
 *  Here is where we actually infer meanings to the members of idprom_t
 */
void
parse_idprom(void)
{
	if (idprom.id_format == IDFORM_1) {
		register int i;

		(void) localetheraddr((struct ether_addr *)idprom.id_ether,
		    (struct ether_addr *)NULL);

		i = idprom.id_machine << 24;
		i = i + idprom.id_serial;
		numtos((ulong_t)i, hw_serial);
	} else
		prom_printf("Invalid format code in IDprom.\n");
}

/*ARGSUSED*/
int
check_status(int id, char *name, dev_info_t *parent)
{
	/* no status check on sun4m */
	return (DDI_SUCCESS);
}


/*
 * Allow for implementation specific correction of PROM property values.
 */

/*ARGSUSED*/
void
impl_fix_props(dev_info_t *dip, dev_info_t *ch_dip, char *name, int len,
    caddr_t buffer)
{
	/*
	 * There are no adjustments needed in this implementation.
	 */
}

/*
 * Wrapper for ddi_prop_lookup_int_array().
 * This is handy because it returns the prop length in
 * bytes which is what most of the callers require.
 */

static int
get_prop_int_array(dev_info_t *di, char *pname, uint_t flags,
    int **pval, uint_t *plen)
{
	int ret;

	if ((ret = ddi_prop_lookup_int_array(DDI_DEV_T_ANY, di,
	    flags | DDI_PROP_DONTPASS, pname, pval, plen))
	    == DDI_PROP_SUCCESS) {
		*plen = (*plen) * (sizeof (int));
	}
	return (ret);
}


/*
 * SECTION: DDI Node Configuration
 */

struct prop_ispec {
	uint_t	pri, vec;
};

/*
 * Create a ddi_parent_private_data structure from the ddi properties of
 * the dev_info node.
 *
 * The "reg" and either an "intr" or "interrupts" properties are required
 * if the driver wishes to create mappings or field interrupts on behalf
 * of the device.
 *
 * The "reg" property is assumed to be a list of at least one triple
 *
 *	<bustype, address, size>*1
 *
 * The "intr" property is assumed to be a list of at least one duple
 *
 *	<SPARC ipl, vector#>*1
 *
 * The "interrupts" property is assumed to be a list of at least one
 * n-tuples that describes the interrupt capabilities of the bus the device
 * is connected to.  For SBus, this looks like
 *
 *	<SBus-level>*1
 *
 * The OBP_RANGES property is optional.
 */
static int
make_ddi_ppd(dev_info_t *child, struct ddi_parent_private_data **ppd,
    int use_registers)
{
	register struct ddi_parent_private_data *pdptr;
	register int n;
	int *reg_prop, *rgstr_prop, *rng_prop, *intr_prop, *irupts_prop;
	uint_t reg_len, rgstr_len, rng_len, intr_len, irupts_len;
	int has_registers = 0;

	*ppd = pdptr = (struct ddi_parent_private_data *)
			kmem_zalloc(sizeof (*pdptr), KM_SLEEP);

	/*
	 * Handle the 'reg'/'registers' properties.
	 * "registers" overrides "reg", but requires that "reg" be exported,
	 * so we can handle wildcard specifiers.  "registers" implies an
	 * sbus style device.  "registers" implies that we insert the
	 * correct value in the regspec_bustype field of each spec for a real
	 * (non-pseudo) device node.  "registers" is a s/w only property, so
	 * we inhibit the prom search for this property.
	 */

	if (get_prop_int_array(child, OBP_REG, 0, &reg_prop, &reg_len)
	    != DDI_PROP_SUCCESS) {
		reg_len = 0;
	}

	rgstr_len = 0;
	if (use_registers)
		(void) get_prop_int_array(child, "registers", DDI_PROP_NOTPROM,
		    &rgstr_prop, &rgstr_len);

	if (rgstr_len != 0)  {

		if (ndi_dev_is_persistent_node(child) && (reg_len != 0))  {

			/*
			 * Convert wildcard "registers" for a real node...
			 * (Else, this is the wildcard prototype node)
			 */
			struct regspec *rp = (struct regspec *)reg_prop;
			uint_t slot = rp->regspec_bustype;
			int i;

			rp = (struct regspec *)rgstr_prop;
			n = rgstr_len / sizeof (struct regspec);
			for (i = 0; i < n; ++i, ++rp)
				rp->regspec_bustype = slot;
		}

		if (reg_len != 0)
			ddi_prop_free((void *)reg_prop);

		reg_prop = rgstr_prop;
		reg_len = rgstr_len;
		++has_registers;
	}
	if ((n = reg_len) != 0)  {
		pdptr->par_nreg = n / (int)sizeof (struct regspec);
		pdptr->par_reg = (struct regspec *)reg_prop;
	}

	/*
	 * See if I have ranges.
	 */
	if (get_prop_int_array(child, OBP_RANGES, 0, &rng_prop,
	    &rng_len) == DDI_PROP_SUCCESS) {
		pdptr->par_nrng = rng_len / (int)(sizeof (struct rangespec));
		pdptr->par_rng = (struct rangespec *)rng_prop;
	}

	/*
	 * Handle the 'intr' and 'interrupts' properties
	 */

	/*
	 * For backwards compatibility with the zillion old SBus cards in
	 * the world, we first look for the 'intr' property for the device.
	 */
	if (get_prop_int_array(child, OBP_INTR, 0, &intr_prop,
	    &intr_len) != DDI_PROP_SUCCESS) {
		intr_len = 0;
	}

	/*
	 * If we're to support bus adapters and future platforms cleanly,
	 * we need to support the generalized 'interrupts' property.
	 */
	if (get_prop_int_array(child, OBP_INTERRUPTS, 0, &irupts_prop,
	    &irupts_len) != DDI_PROP_SUCCESS) {
		irupts_len = 0;
	} else if (intr_len != 0) {
		/*
		 * If both 'intr' and 'interrupts' are defined,
		 * then 'interrupts' wins and we toss the 'intr' away.
		 */
		ddi_prop_free((void *)intr_prop);
		intr_len = 0;
	}

	if (intr_len != 0) {

		/*
		 * Translate the 'intr' property into an array
		 * an array of struct intrspec's.  There's not really
		 * very much to do here except copy what's out there.
		 */

		struct intrspec *new;
		struct prop_ispec *l;

		n = pdptr->par_nintr =
			intr_len / sizeof (struct prop_ispec);
		l = (struct prop_ispec *)intr_prop;
		new = pdptr->par_intr = (struct intrspec *)
		    kmem_zalloc(n * sizeof (struct intrspec), KM_SLEEP);
		while (n--) {
			new->intrspec_pri = l->pri;
			new->intrspec_vec = l->vec;
			new++;
			l++;
		}
		ddi_prop_free((void *)intr_prop);

	} else if ((n = irupts_len) != 0) {
		size_t size;
		int *out;

		/*
		 * Translate the 'interrupts' property into an array
		 * of intrspecs for the rest of the DDI framework to
		 * toy with.  Only our ancestors really know how to
		 * do this, so ask 'em.  We massage the 'interrupts'
		 * property so that it is pre-pended by a count of
		 * the number of integers in the argument.
		 */
		size = sizeof (int) + n;
		out = kmem_alloc(size, KM_SLEEP);
		*out = n / sizeof (int);
		bcopy((caddr_t)irupts_prop, (caddr_t)(out + 1), (size_t)n);
		ddi_prop_free((void *)irupts_prop);
		if (ddi_ctlops(child, child, DDI_CTLOPS_XLATE_INTRS,
		    out, pdptr) != DDI_SUCCESS) {
			cmn_err(CE_CONT,
			    "Unable to translate 'interrupts' for %s%d\n",
			    DEVI(child)->devi_binding_name,
			    DEVI(child)->devi_instance);
		}
		kmem_free(out, size);
	}
	return (has_registers);
}


/*
 * Called from the bus_ctl op of sunbus (sbus, obio, etc) nexus drivers
 * to implement the DDI_CTLOPS_INITCHILD operation.  That is, it names
 * the children of sun busses based on the reg spec.
 *
 * Handles the following properties:
 *
 *	Property		value
 *	  Name			type
 *
 *	reg		register spec
 *	registers	wildcard s/w sbus register spec (.conf file property)
 *	intr		old-form interrupt spec
 *	interrupts	new (bus-oriented) interrupt spec
 *	ranges		range spec
 */

static char *cantmerge = "Cannot merge %s devinfo node %s@%s";

static int
i_impl_ddi_sunbus_initchild(dev_info_t *child, int use_registers)
{
	struct ddi_parent_private_data *pdptr;
	char name[MAXNAMELEN];
	int has_registers;
	dev_info_t *parent, *och;
	void impl_ddi_sunbus_removechild(dev_info_t *);

	/*
	 * Fill in parent-private data and this function returns to us
	 * an indication if it used "registers" to fill in the data.
	 */
	has_registers = make_ddi_ppd(child, &pdptr, use_registers);
	ddi_set_parent_data(child, (caddr_t)pdptr);
	parent = ddi_get_parent(child);

	/*
	 * If this is a s/w node defined with the "registers" property,
	 * this means that this is an "sbus" style device and that this
	 * is a wildcard specifier, whose properties get applied to all
	 * previously defined h/w nodes with the same name and same parent.
	 *
	 * XXX: This functionality is "sbus" class nexus specific...
	 * XXX: and should be a function of that nexus driver only!
	 */

	if ((has_registers) && (ndi_dev_is_persistent_node(child) == 0)) {

		major_t major;
		int need_init;

		/*
		 * Find all previously defined h/w children with the same name
		 * and same parent and copy the property lists from the
		 * prototype node into the h/w nodes and re-inititialize them.
		 */

		if ((major = ddi_name_to_major(ddi_get_name(child))) == -1)  {
			impl_ddi_sunbus_removechild(child);
			return (DDI_NOT_WELL_FORMED);
		}

		for (och = devnamesp[major].dn_head;
		    (och != NULL) && (och != child);
		    och = ddi_get_next(och))  {

			if ((ddi_get_parent(och) != parent) ||
			    (ndi_dev_is_persistent_node(och) == 0))
				continue;
			if (strcmp(ddi_get_name(och), ddi_get_name(child)) != 0)
				continue;

			if (DEVI(och)->devi_sys_prop_ptr ||
			    DEVI(och)->devi_drv_prop_ptr || DDI_CF2(och)) {
				cmn_err(CE_WARN, cantmerge, "wildcard",
				    ddi_get_name(och), ddi_get_name_addr(och));
				continue;
			}

			/*
			 * If the node we're merging into is already init-ed
			 * (CF1), we need to un-init it and later, re-init it.
			 */
			need_init = DDI_CF1(och) ? 1 : 0;
			if (need_init)
				(void) ddi_uninitchild(och);

			mutex_enter(&(DEVI(child)->devi_lock));
			mutex_enter(&(DEVI(och)->devi_lock));
			copy_prop(DEVI(child)->devi_drv_prop_ptr,
			    &(DEVI(och)->devi_drv_prop_ptr));
			copy_prop(DEVI(child)->devi_sys_prop_ptr,
			    &(DEVI(och)->devi_sys_prop_ptr));
			mutex_exit(&(DEVI(och)->devi_lock));
			mutex_exit(&(DEVI(child)->devi_lock));

			if (need_init)
				(void) ddi_initchild(parent, och);
		}

		/*
		 * We can toss the wildcard node...
		 */
		impl_ddi_sunbus_removechild(child);
		return (DDI_NOT_WELL_FORMED);
	}

	/*
	 * Force the name property to be generated from the "reg" property...
	 * (versus the "registers" property, so we always match the obp
	 * namespace no matter what the .conf file said.)
	 */

	name[0] = '\0';
	if ((has_registers) && ndi_dev_is_persistent_node(child)) {

		int *reg_prop;
		uint_t reg_len;

		if (get_prop_int_array(child, OBP_REG, 0, &reg_prop,
		    &reg_len) == DDI_PROP_SUCCESS)  {

			struct regspec *rp = (struct regspec *)reg_prop;

			(void) sprintf(name, "%x,%x", rp->regspec_bustype,
			    rp->regspec_addr);
			ddi_prop_free((void *)reg_prop);
		}

	} else if (sparc_pd_getnreg(child) > 0) {
		(void) sprintf(name, "%x,%x",
		    (uint_t)sparc_pd_getreg(child, 0)->regspec_bustype,
		    (uint_t)sparc_pd_getreg(child, 0)->regspec_addr);
	}

	ddi_set_name_addr(child, name);
	/*
	 * If another child already exists by this name,
	 * merge these properties onto that one.
	 * NOTE - This property override/merging depends on several things:
	 * 1) That hwconf nodes are 'named' (ddi_initchild()) before prom
	 *	devinfo nodes.
	 * 2) That ddi_findchild() will call ddi_initchild() for all
	 *	siblings with a matching devo_name field.
	 * 3) That hwconf devinfo nodes come "after" prom devinfo nodes.
	 *
	 * Then "och" should be a prom node with no attached properties.
	 */
	if ((och = ddi_findchild(parent, ddi_get_name(child), name)) != NULL &&
	    och != child) {
		if ((ndi_dev_is_persistent_node(och) == 0) ||
		    ndi_dev_is_persistent_node(child) ||
		    DEVI(och)->devi_sys_prop_ptr ||
		    DEVI(och)->devi_drv_prop_ptr || DDI_CF2(och)) {
			cmn_err(CE_WARN, cantmerge, "hwconf",
			    ddi_get_name(child), name);
			impl_ddi_sunbus_removechild(child);
			return (DDI_NOT_WELL_FORMED);
		}
		/*
		 * Move "child"'s properties to "och." and allow the node
		 * to be init-ed (this handles 'reg' and 'intr/interrupts'
		 * in hwconf files overriding those in a hw node)
		 *
		 * Note that 'och' is not yet in canonical form 2, so we
		 * can happily transform it to prototype form and recreate it.
		 */
		(void) ddi_uninitchild(och);
		mutex_enter(&(DEVI(child)->devi_lock));
		mutex_enter(&(DEVI(och)->devi_lock));
		DEVI(och)->devi_sys_prop_ptr = DEVI(child)->devi_sys_prop_ptr;
		DEVI(och)->devi_drv_prop_ptr = DEVI(child)->devi_drv_prop_ptr;
		DEVI(child)->devi_sys_prop_ptr = NULL;
		DEVI(child)->devi_drv_prop_ptr = NULL;
		mutex_exit(&(DEVI(och)->devi_lock));
		mutex_exit(&(DEVI(child)->devi_lock));
		(void) ddi_initchild(parent, och);
		/*
		 * To get rid of this child...
		 */
		impl_ddi_sunbus_removechild(child);
		return (DDI_NOT_WELL_FORMED);
	}
	return (DDI_SUCCESS);
}

/*
 * non-sbus adaptors use this function, (until we can change the rest
 * of the nexi to use a common function that passes in requesting parent
 * dip, or break this entire mess up.
 */
int
impl_ddi_sunbus_initchild(dev_info_t *child)
{
	return (i_impl_ddi_sunbus_initchild(child, 0));
}

/*
 * sbus adaptors (that support s/w registers wildcarding) use this function
 */
int
impl_ddi_sbus_initchild(dev_info_t *child)
{
	return (i_impl_ddi_sunbus_initchild(child, 1));
}

void
impl_ddi_sunbus_removechild(dev_info_t *dip)
{
	register struct ddi_parent_private_data *pdptr;
	register size_t n;

	if ((pdptr = (struct ddi_parent_private_data *)
	    ddi_get_parent_data(dip)) != NULL)  {
		if ((n = (size_t)pdptr->par_nintr) != 0) {
			/*
			 * Note that kmem_free is used here (instead of
			 * ddi_prop_free) because the contents of the
			 * property were placed into a separate buffer and
			 * mucked with a bit before being stored in par_intr.
			 * The actual return value from the prop lookup
			 * was freed with ddi_prop_free previously.
			 */
			kmem_free(pdptr->par_intr, n *
			    sizeof (struct intrspec));
		}

		if ((n = (size_t)pdptr->par_nrng) != 0)
			ddi_prop_free((void *)pdptr->par_rng);

		if ((n = pdptr->par_nreg) != 0)
			ddi_prop_free((void *)pdptr->par_reg);

		kmem_free(pdptr, sizeof (*pdptr));
		ddi_set_parent_data(dip, NULL);
	}
	ddi_set_name_addr(dip, NULL);
	/*
	 * Strip the node to properly convert it back to prototype form
	 */
	ddi_remove_minor_node(dip, NULL);
	impl_rem_dev_props(dip);
}

/*
 * SECTION: DDI Interrupt
 */
int
i_ddi_intr_hilevel(dev_info_t *dip, uint_t inumber)
{
	ddi_intrspec_t ispec;
	int	r = 0;

	/*
	 * Get the named interrupt specification.  If found, perform
	 * the bus op to find out whether it is hilevel or not.
	 */
	ispec = i_ddi_get_intrspec(dip, dip, inumber);

	if (ispec != NULL)
		(void) ddi_ctlops(dip, dip, DDI_CTLOPS_INTR_HILEVEL,
		    (void *)ispec, (void *)&r);

	return (r);
}

int
i_ddi_add_intr(dev_info_t *dip, uint_t inumber,
    ddi_iblock_cookie_t *iblock_cookiep,
    ddi_idevice_cookie_t *idevice_cookiep,
    uint_t (*int_handler)(caddr_t int_handler_arg),
    caddr_t int_handler_arg)
{
	ddi_intrspec_t ispec;

	/* get the named interrupt specification */
	if ((ispec = i_ddi_get_intrspec(dip, dip, inumber)) == NULL)
		return (DDI_INTR_NOTFOUND);

	/* request the parent node to add it */
	return (i_ddi_add_intrspec(dip, dip, ispec, iblock_cookiep,
	    idevice_cookiep, int_handler, int_handler_arg,
	    IDDI_INTR_TYPE_NORMAL));
}

int
i_ddi_add_fastintr(dev_info_t *dip, uint_t inumber,
    ddi_iblock_cookie_t *iblock_cookiep,
    ddi_idevice_cookie_t *idevice_cookiep,
    uint_t (*hi_int_handler)(void))
{
	ddi_intrspec_t ispec;

	/* get the named interrupt specification */
	if ((ispec = i_ddi_get_intrspec(dip, dip, inumber)) == NULL) {
		return (DDI_INTR_NOTFOUND);
	}

	/* request the parent node to add it */
	return (i_ddi_add_intrspec(dip, dip, ispec, iblock_cookiep,
	    idevice_cookiep, (uint_t (*)(caddr_t))hi_int_handler, 0,
	    IDDI_INTR_TYPE_FAST));
}

void
i_ddi_remove_intr(dev_info_t *dip, uint_t inum,
    ddi_iblock_cookie_t iblock_cookie)
{
	ddi_intrspec_t ispec;

	/* get the named interrupt specification */
	if ((ispec = i_ddi_get_intrspec(dip, dip, inum)) != NULL) {
		/* request the parent node to remove it */
		i_ddi_remove_intrspec(dip, dip, ispec, iblock_cookie);
	}
}

int
i_ddi_dev_nintrs(dev_info_t *dev, int *result)
{
	return (ddi_ctlops(dev, dev, DDI_CTLOPS_NINTRS,
	    (void *)0, (void *)result));
}

/*
 * i_ddi_add_softintr - add a soft interrupt to the system
 */
int
i_ddi_add_softintr(dev_info_t *rdip, int preference, ddi_softintr_t *idp,
	ddi_iblock_cookie_t *iblock_cookiep,
	ddi_idevice_cookie_t *idevice_cookiep,
	uint_t (*int_handler)(caddr_t int_handler_arg),
	caddr_t int_handler_arg)
{
	dev_info_t *rp;
	struct soft_intrspec *sspec;
	struct intrspec *ispec;
	int r;

	if (idp == NULL)
		return (DDI_FAILURE);
	sspec = (struct soft_intrspec *)kmem_zalloc(sizeof (*sspec), KM_SLEEP);
	sspec->si_devi = (struct dev_info *)rdip;
	ispec = &sspec->si_intrspec;
	if (preference > DDI_SOFTINT_MED) {
		ispec->intrspec_pri = 6;
	} else {
		ispec->intrspec_pri = 4;
	}
	rp = ddi_root_node();
	r = (*(DEVI(rp)->devi_ops->devo_bus_ops->bus_add_intrspec))(rp,
	    rdip, (ddi_intrspec_t)ispec, iblock_cookiep, idevice_cookiep,
	    int_handler, int_handler_arg, IDDI_INTR_TYPE_SOFT);
	if (r != DDI_SUCCESS) {
		kmem_free((caddr_t)sspec, sizeof (*sspec));
		return (r);
	}
	*idp = (ddi_softintr_t)sspec;
	return (DDI_SUCCESS);
}

void
i_ddi_trigger_softintr(ddi_softintr_t id)
{
	uint_t pri = ((struct soft_intrspec *)id)->si_intrspec.intrspec_pri;
	/* ICK! */
	setsoftint(INT_IPL(pri));
}

void
i_ddi_remove_softintr(ddi_softintr_t id)
{
	struct soft_intrspec *sspec = (struct soft_intrspec *)id;
	struct intrspec *ispec = &sspec->si_intrspec;
	dev_info_t *rp = ddi_root_node();
	dev_info_t *rdip = (dev_info_t *)sspec->si_devi;

	(*(DEVI(rp)->devi_ops->devo_bus_ops->bus_remove_intrspec))(rp,
	    rdip, (ddi_intrspec_t)ispec, (ddi_iblock_cookie_t)0);
	kmem_free((caddr_t)sspec, sizeof (*sspec));
}


/* New interrupt architecture not implemented on 4m machines. */
/*ARGSUSED*/
int
i_ddi_intr_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_ctlop_t op,
    void *arg, void *val)
{
	return (DDI_FAILURE);
}


#define	OVECTOR(n)		\
int	olvl##n##_spurious;	\
struct autovec olvl##n[NVECT]

#define	SVECTOR(n)		\
int	slvl##n##_spurious;	\
struct autovec slvl##n[NVECT]

#define	VVECTOR(n)		\
int	vlvl##n##_spurious;	\
struct autovec vlvl##n[NVECT]

#define	XVECTOR(n)		\
int	xlvl##n##_spurious;	\
struct autovec xlvl##n[NVECT]

#define	VECTOR(n)		\
int	level##n##_spurious;	\
struct autovec level##n[NVECT]

/*ARGSUSED*/
static uint_t
softlevel1(caddr_t arg)
{
	softint();
	return (1);
}

/*
 * These structures are used in locore.s to jump to device interrupt routines.
 * They also provide vmstat assistance.
 * They will index into the string table generated by autoconfig
 * but in the exact order addintr sees them. This allows IOINTR to quickly
 * find the right counter to increment.
 * (We use the fact that the arrays are initialized to 0 by default).
 */

/*
 * Initial interrupt vector information.
 * Each of these macros defines both the "spurious-int" counter and
 * the list of autovec structures that will be used by locore.s
 * to distribute interrupts to the interrupt requestors.
 * Each list is terminated by a null.
 * Lists are scanned only as needed: hard ints
 * stop scanning when the int is claimed; soft ints
 * scan the entire list. If nobody on the list claims the
 * interrupt, then a spurious interrupt is reported.
 *
 * These should all be initialized to zero, except for the
 * few interrupts that we have handlers for built into the
 * kernel that are not installed by calling "addintr".
 * I would like to eventually get everything going through
 * the "addintr" path.
 * It might be a good idea to remove VECTORs that are not
 * actually processed by locore.s
 */

				/* onboard interrupts */
OVECTOR(1) = {{0}};
OVECTOR(2) = {{0}};
OVECTOR(3) = {{0}};
OVECTOR(4) = {{0}};		/* scsi */
OVECTOR(5) = {{0}};
OVECTOR(6) = {{0}};		/* ethernet */
OVECTOR(7) = {{0}};
OVECTOR(8) = {{0}};		/* video */
OVECTOR(9) = {{0}};		/* module int */
OVECTOR(10) = {{0}};		/* system timer */
OVECTOR(11) = {{0}};
OVECTOR(12) = {{0}};		/* serial ports */
OVECTOR(13) = {{0}};
OVECTOR(14) = {{0}};		/* processor timer */
OVECTOR(15) = {{0}};		/* async errors */

				/* S-Bus Interrupts */
SVECTOR(1) = {{0}};
SVECTOR(2) = {{0}};
SVECTOR(3) = {{0}};
SVECTOR(4) = {{0}};
SVECTOR(5) = {{0}};
SVECTOR(6) = {{0}};
SVECTOR(7) = {{0}};

				/* software vectored interrupts */
XVECTOR(1) = {{softlevel1}, {0}};	/* time-scheduled tasks */
XVECTOR(2) = {{0}};
XVECTOR(3) = {{0}};
XVECTOR(4) = {{0}};
XVECTOR(5) = {{0}};
XVECTOR(6) = {{0}};
XVECTOR(7) = {{0}};
XVECTOR(8) = {{timeout_cei}, {0}};	/* enable correctable memory errors */
XVECTOR(9) = {{0}};
/* levels 10..13: forwarding is responsibility of service routine */
XVECTOR(10) = {{0}};
XVECTOR(11) = {{0}};
XVECTOR(12) = {{process_aflt}, {0}};	/* process async. fault */
XVECTOR(13) = {{0}};
XVECTOR(14) = {{0}};	/* process switch request - poke_cpu */
XVECTOR(15) = {{0}};
				/* otherwise unclaimed sparc interrupts */
VECTOR(1) = {{0}};
VECTOR(2) = {{0}};
VECTOR(3) = {{0}};
VECTOR(4) = {{0}};
VECTOR(5) = {{0}};
VECTOR(6) = {{0}};
VECTOR(7) = {{0}};
VECTOR(8) = {{0}};
VECTOR(9) = {{0}};
VECTOR(10) = {{0}};
VECTOR(11) = {{0}};
VECTOR(12) = {{0}};
VECTOR(13) = {{0}};
VECTOR(14) = {{0}};
VECTOR(15) = {{0}};

/*
 * indirection table, to save us some large switch statements
 * NOTE: This must agree with "INTLEVEL_foo" constants in
 *	<sys/avintr.h>
 */
struct autovec *const vectorlist[] = {
/*
 * otherwise unidentified interrupts at SPARC levels 1..15
 */
	0,	level1,	level2,  level3,  level4,  level5,  level6,  level7,
	level8,	level9,	level10, level11, level12, level13, level14, level15,
/*
 * interrupts identified as "soft"
 */
	0,	xlvl1,	xlvl2,	xlvl3,	xlvl4,	xlvl5,	xlvl6,	xlvl7,
	xlvl8,	xlvl9,	xlvl10,	xlvl11,	xlvl12,	xlvl13,	xlvl14,	xlvl15,
/*
 * interrupts identified as "onboard"
 */
	0,	olvl1,	olvl2,	olvl3,	olvl4,	olvl5,	olvl6,	olvl7,
	olvl8,	olvl9,	olvl10,	olvl11,	olvl12,	olvl13,	olvl14,	olvl15,
/*
 * interrupts identified as "sbus" -- note that sbus has only level 1
 * through 7; these are mapped to the proper sparc levels via the
 * boot prom forth word "???".
 */
	0,	0,	slvl1,	slvl2,	0,	slvl3,	0,	slvl4,
	0,	slvl5,	0,	slvl6,	0,	slvl7,	0,	0,
};

/*
 * This value is exported here for the functions in avintr.c
 */
const uint_t maxautovec = (sizeof (vectorlist) / sizeof (vectorlist[0]));

/*
 * This table gives the mapping from onboard SBus level to sparc ipl.
 *
 * The fact that it's here (rather than in the 'sbus' nexus driver)
 * can be construed as a bug.  However, it's really a workaround for
 * the fact that we changed the specification of the interrupt mappings
 * between sun4c and sun4m architectures.  The way we should've made
 * such a non-backwards compatible change would've been to have changed
 * the 'name' property of the SBus nexus driver.  Oh well ..
 */
const char sbus_to_sparc_tbl[] = {
	-1,	2,	3,	5,	7,	9,	11,	13
};

/*
 * This is part of a comment left over from when add_interrupt used to live
 * here.
 * It did not follow it to avintr.c because it is sun4m specific info.
 * NOTE: if a device can generate interrupts on more than
 * one level, or if a driver services devices that interrupt
 * on more than one level, then the driver should install
 * itself on each of those levels.
 *
 * devices may also install themselves on the  Internal, or SBUS
 * vector lists using inerrupt level offsets, see <sun/autoconf.h>.
 *
 * On Hard-ints, order of evaluation of the chains is:
 * if onboard int at this level active in SIPR,
 *	scan "onboard" chain; if nobody claims,
 *	report spurious onboard interrupt.
 * if sbus int at this level active in SIPR,
 *	scan "sbus" chain; if nobody claims,
 *	report spurious sbus interrupt.
 * scan "unspecified" chain; if nobody claims,
 *	report spurious interrupt.
 * Scanning terminates with the first driver that claims it has
 * serviced the interrupt.
 *
 * On Soft-ints, order of evaulation of the chains is:
 *   scan the "soft" chain
 *   scan the "unspecified" chain
 * Scanning continues until all service routines have been called.
 * If nobody claims the interrupt, report a spurious soft interrupt.
 */

/*
 * Check for machine specific interrupt levels which cannot be reasigned by
 * settrap(), sun4m version.
 *
 * Since sun4m has shared interrupt vectors, we cannot have settrap() reassign
 * them.  If a fast trap is really really needed, a special trap handler
 * must be written that separates the desired fast trap interrupt from the
 * other interrupts that share the same level.  The fast trap interrupt can
 * then be vectored to the driver's fast trap handler, while the other
 * interrupts continue on to the autovector code.
 */
/*ARGSUSED*/
int
exclude_settrap(int lvl)
{
	return (1);
}

/*
 * Check for machine specific interrupt levels which cannot have interrupt
 * handlers added.  There are none on the sun4m.
 */
/*ARGSUSED*/
int
exclude_level(int lvl)
{
	return (0);
}


/*
 * SECTION: DDI Memory/DMA
 */

int
i_ddi_mem_alloc(dev_info_t *dip, ddi_dma_attr_t *attr,
	size_t length, int cansleep, int streaming,
	ddi_device_acc_attr_t *accattrp, caddr_t *kaddrp,
	size_t *real_length, ddi_acc_hdl_t *ap)
{
	caddr_t a;
	int iomin, align, nocache;

#if defined(lint)
	accattrp = accattrp;
#endif
	/*
	 * Check legality of arguments
	 */
	if (length == 0 || kaddrp == NULL || attr == NULL) {
		return (DDI_FAILURE);
	}
	if (attr->dma_attr_minxfer == 0 || attr->dma_attr_align == 0 ||
		(attr->dma_attr_align & (attr->dma_attr_align - 1)) ||
		(attr->dma_attr_minxfer & (attr->dma_attr_minxfer - 1))) {
			return (DDI_FAILURE);
	}

	/*
	 * Drivers for 64-bit capable SBus devices will encode
	 * the burtsizes for 64-bit xfers in the upper 16-bits.
	 * For DMA alignment, we use the most restrictive
	 * alignment of 32-bit and 64-bit xfers.
	 */
	iomin = (attr->dma_attr_burstsizes & 0xffff) |
			((attr->dma_attr_burstsizes >> 16) & 0xffff);
	/*
	 * If a driver set burtsizes to 0, we give him byte alignment.
	 * Otherwise align at the burtsizes boundary.
	 */
	if (iomin == 0)
		iomin = 1;
	else
		iomin = 1 << (ddi_fls(iomin) - 1);
	iomin = maxbit(iomin, attr->dma_attr_minxfer);
	iomin = maxbit(iomin, attr->dma_attr_align);
	iomin = ddi_iomin(dip, iomin, streaming);
	if (iomin == 0)
		return (DDI_FAILURE);

	ASSERT((iomin & (iomin - 1)) == 0);
	ASSERT(iomin >= attr->dma_attr_minxfer);
	ASSERT(iomin >= attr->dma_attr_align);

	length = P2ROUNDUP(length, iomin);
	align = iomin;

	/*
	 * Allocate the requested amount from the the system
	 */
	nocache = !streaming && !(cache & CACHE_IOCOHERENT);
	if (ross_iopb_workaround)
		nocache = 1;	/* ross bug; fixed in A.8 */
	a = kalloca(length, align, nocache, cansleep);
	if ((*kaddrp = a) == 0) {
		return (DDI_FAILURE);
	} else {
		if (real_length) {
			*real_length = length;
		}
		if (ap) {
			/*
			 * assign handle information
			 */
			impl_acc_hdl_init(ap);
		}
		return (DDI_SUCCESS);
	}
}

/*
 * covert old DMA limits structure to DMA attribute structure
 * and continue.
 */
int
i_ddi_mem_alloc_lim(dev_info_t *dip, ddi_dma_lim_t *limits,
	uint_t length, int cansleep, int streaming,
	ddi_device_acc_attr_t *accattrp, caddr_t *kaddrp,
	uint_t *real_length, ddi_acc_hdl_t *ap)
{
	ddi_dma_attr_t dma_attr, *attrp;
	size_t rlen;
	int ret;

	/*
	 * set up DMA attribute structure to pass to i_ddi_mem_alloc()
	 */
	ASSERT(limits);
	attrp = &dma_attr;
	attrp->dma_attr_version = DMA_ATTR_V0;
	attrp->dma_attr_addr_lo = (uint64_t)limits->dlim_addr_lo;
	attrp->dma_attr_addr_hi = (uint64_t)limits->dlim_addr_hi;
	attrp->dma_attr_count_max = (uint64_t)-1;
	attrp->dma_attr_align = 1;
	attrp->dma_attr_burstsizes = (uint_t)limits->dlim_burstsizes;
	attrp->dma_attr_minxfer = (uint32_t)limits->dlim_minxfer;
	attrp->dma_attr_maxxfer = (uint64_t)-1;
	attrp->dma_attr_seg = (uint64_t)limits->dlim_cntr_max;
	attrp->dma_attr_sgllen = 1;
	attrp->dma_attr_granular = 1;
	attrp->dma_attr_flags = 0;

	ret = i_ddi_mem_alloc(dip, attrp, (size_t)length, cansleep, streaming,
			accattrp, kaddrp, &rlen, ap);
	if (ret == DDI_SUCCESS) {
		if (real_length)
			*real_length = (uint_t)rlen;
	}
	return (ret);
}

void
i_ddi_mem_free(caddr_t kaddr, int stream)
{
	int nocache;

	nocache = !stream && !(cache & CACHE_IOCOHERENT);
	if (ross_iopb_workaround)
		nocache = 1;	/* ross bug; fixed in A.8 */
	kfreea(kaddr, nocache);
}


/*
 * SECTION: DDI Data Access
 */

int
i_ddi_poke(dev_info_t *dip, size_t size, void *addr, void *val)
{
	return (common_ddi_poke(dip, size, addr, val));
}

int
i_ddi_peek(dev_info_t *dip, size_t size, void *addr, void *val_p)
{
	return (common_ddi_peek(dip, size, addr, val_p));
}


/*
 * SECTION: Misc Functions
 */

/*
 * Implementation wrapper functions
 */
/*ARGSUSED*/
uint_t
impl_assign_instance(dev_info_t *dip)
{
	return ((uint_t)-1);
}

/*ARGSUSED*/
int
impl_keep_instance(dev_info_t *dip)
{
	return (DDI_FAILURE);
}

/*ARGSUSED*/
int
impl_free_instance(dev_info_t *dip)
{
	return (DDI_FAILURE);
}

/*ARGSUSED*/
int
impl_check_cpu(dev_info_t *devi)
{
	return (DDI_SUCCESS);
}

/*
 * Called from common/cpr_driver.c: Power off machine
 * Let the firmware remove power if it can.
 */
void
arch_power_down()
{
	int is_defined = 0;
	char *wordexists = "p\" power-off\" find nip swap ! ";

	/*
	 * is_defined has value -1 when defined
	 */
	prom_interpret(wordexists, (int)(&is_defined), 0, 0, 0, 0);
	if (is_defined)
		prom_interpret("power-off", 0, 0, 0, 0, 0);
}
