/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * fcgp2.c: Framework gp2 (Safari) fcode ops
 */
#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/systm.h>
#include <sys/pci.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/ddidmareq.h>
#include <sys/modctl.h>
#include <sys/ndi_impldefs.h>
#include <sys/fcode.h>
#include <sys/promif.h>
#include <sys/promimpl.h>

static int gfc_map_in(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_map_out(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_register_fetch(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_register_store(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_claim_address(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_claim_memory(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_release_memory(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_vtop(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_master_intr(dev_info_t *, fco_handle_t, fc_ci_t *);

static int gfc_config_child(dev_info_t *, fco_handle_t, fc_ci_t *);

static int gfc_get_fcode_size(dev_info_t *, fco_handle_t, fc_ci_t *);
static int gfc_get_fcode(dev_info_t *, fco_handle_t, fc_ci_t *);

int prom_get_fcode_size(char *);
int prom_get_fcode(char *, char *);

int fcpci_unloadable;
int no_advisory_dma;

#define	HIADDR(n) ((uint32_t)(((uint64_t)(n) & 0xFFFFFFFF00000000)>> 32))
#define	LOADDR(n)((uint32_t)((uint64_t)(n) & 0x00000000FFFFFFFF))
#define	LADDR(lo, hi)    (((uint64_t)(hi) << 32) | (uint32_t)(lo))
#define	PCI_4GIG_LIMIT 0xFFFFFFFFUL


/*
 * Module linkage information for the kernel.
 */
static struct modlmisc modlmisc = {
	&mod_miscops, "FCode gp2 (safari) bus functions"
};

static struct modlinkage modlinkage = {
	MODREV_1, (void *)&modlmisc, NULL
};

int
_init(void)
{
	return (mod_install(&modlinkage));
}

int
_fini(void)
{
	if (fcpci_unloadable)
		return (mod_remove(&modlinkage));
	return (EBUSY);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}


struct gfc_ops_v {
	char *svc_name;
	fc_ops_t *f;
};

struct gfc_ops_v gp2_pov[] = {
	{	"map-in",		gfc_map_in},
	{	"map-out",		gfc_map_out},
	{	"rx@",			gfc_register_fetch},
	{	"rl@",			gfc_register_fetch},
	{	"rw@",			gfc_register_fetch},
	{	"rb@",			gfc_register_fetch},
	{	"rx!",			gfc_register_store},
	{	"rl!",			gfc_register_store},
	{	"rw!",			gfc_register_store},
	{	"rb!",			gfc_register_store},
	{	"claim-address",	gfc_claim_address},
	{	"master-interrupt",	gfc_master_intr},
	{	"claim-memory",		gfc_claim_memory},
	{	"release-memory",	gfc_release_memory},
	{	"vtop",			gfc_vtop},
	{	FC_CONFIG_CHILD,	gfc_config_child},
	{	FC_GET_FCODE_SIZE,	gfc_get_fcode_size},
	{	FC_GET_FCODE,		gfc_get_fcode},
	{	NULL,			NULL}
};

struct gfc_ops_v gp2_shared_pov[] = {
	{	NULL,			NULL}
};

static int gp2_map_phys(dev_info_t *, struct regspec *,  caddr_t *,
    ddi_device_acc_attr_t *, ddi_acc_handle_t *);
static void gp2_unmap_phys(ddi_acc_handle_t *);

fco_handle_t
gp2_fc_ops_alloc_handle(dev_info_t *ap, dev_info_t *child,
    void *fcode, size_t fcode_size, char *unit_address,
    char *my_args)
{
	fco_handle_t rp;
	phandle_t h;

	rp = kmem_zalloc(sizeof (struct fc_resource_list), KM_SLEEP);
	rp->next_handle = fc_ops_alloc_handle(ap, child, fcode, fcode_size,
	    unit_address, NULL);
	rp->ap = ap;
	rp->child = child;
	rp->fcode = fcode;
	rp->fcode_size = fcode_size;
	rp->my_args = my_args;

	if (unit_address) {
		char *buf;

		buf = kmem_zalloc(strlen(unit_address) + 1, KM_SLEEP);
		(void) strcpy(buf, unit_address);
		rp->unit_address = buf;
	}

	/*
	 * Add the child's nodeid to our table...
	 */
	h = ddi_get_nodeid(rp->child);
	fc_add_dip_to_phandle(fc_handle_to_phandle_head(rp), rp->child, h);

	return (rp);
}

void
gp2_fc_ops_free_handle(fco_handle_t rp)
{
	struct fc_resource *ip, *np;

	ASSERT(rp);

	if (rp->next_handle)
		fc_ops_free_handle(rp->next_handle);
	if (rp->unit_address)
		kmem_free(rp->unit_address, strlen(rp->unit_address) + 1);
	if (rp->my_args != NULL)
		kmem_free(rp->my_args, strlen(rp->my_args) + 1);

	/*
	 * Release all the resources from the resource list
	 */
	for (ip = rp->head; ip != NULL; ip = np) {
		np = ip->next;
		switch (ip->type) {
		case RT_MAP:
			FC_DEBUG1(1, CE_CONT, "gp2_fc_ops_free: "
			    " map handle - %p\n", ip->fc_map_handle);
			break;
		case RT_DMA:
			/* DMA has to be freed up at exit time */
			cmn_err(CE_CONT, "gfc_fc_ops_free: DMA seen!\n");
			break;
		case RT_CONTIGIOUS:
			FC_DEBUG2(1, CE_CONT, "gp2_fc_ops_free: "
			    "Free claim-memory resource 0x%lx size 0x%x\n",
			    ip->fc_contig_virt, ip->fc_contig_len);

			(void) ndi_ra_free(ddi_root_node(),
			    (uint64_t)ip->fc_contig_virt,
			    ip->fc_contig_len, "gptwo-contigousmem",
			    NDI_RA_PASS);

			break;
		default:
			cmn_err(CE_CONT, "gp2_fc_ops_free: "
			    "unknown resource type %d\n", ip->type);
			break;
		}
		fc_rem_resource(rp, ip);
		kmem_free(ip, sizeof (struct fc_resource));
	}
	kmem_free(rp, sizeof (struct fc_resource_list));
}

int
gp2_fc_ops(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	struct gfc_ops_v *pv;
	char *name = fc_cell2ptr(cp->svc_name);

	ASSERT(rp);

	/*
	 * First try the generic fc_ops. If the ops is a shared op,
	 * also call our local function.
	 */
	if (fc_ops(ap, rp->next_handle, cp) == 0) {
		for (pv = gp2_shared_pov; pv->svc_name != NULL; ++pv)
			if (strcmp(pv->svc_name, name) == 0)
				return (pv->f(ap, rp, cp));
		return (0);
	}

	for (pv = gp2_pov; pv->svc_name != NULL; ++pv)
		if (strcmp(pv->svc_name, name) == 0)
			return (pv->f(ap, rp, cp));

	FC_DEBUG1(9, CE_CONT, "gp2_fc_ops: <%s> not serviced\n", name);

	return (-1);
}

/*
 * map-in  (phys.lo phys.hi size -- virt )
 */
static int
gfc_map_in(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	size_t len;
	int error;
	caddr_t virt;
	struct fc_resource *ip;
	struct regspec r;
	ddi_device_acc_attr_t acc;
	ddi_acc_handle_t h;

	if (fc_cell2int(cp->nargs) != 3)
		return (fc_syntax_error(cp, "nargs must be 3"));

	if (fc_cell2int(cp->nresults) < 1)
		return (fc_syntax_error(cp, "nresults must be >= 1"));

	r.regspec_size = len = fc_cell2size(fc_arg(cp, 0));
	r.regspec_bustype = fc_cell2uint(fc_arg(cp, 1));
	r.regspec_addr = fc_cell2uint(fc_arg(cp, 2));

	acc.devacc_attr_version = DDI_DEVICE_ATTR_V0;
	acc.devacc_attr_endian_flags = DDI_STRUCTURE_BE_ACC;
	acc.devacc_attr_dataorder = DDI_STRICTORDER_ACC;

	FC_DEBUG3(1, CE_CONT, "gfc_map_in: attempting map in "
	    "address 0x%08x.%08x length %x\n", r.regspec_bustype,
	    r.regspec_addr, r.regspec_size);

	error = gp2_map_phys(rp->child, &r, &virt, &acc, &h);

	if (error)  {
		FC_DEBUG3(1, CE_CONT, "gfc_map_in: map in failed - "
		    "address 0x%08x.%08x length %x\n", r.regspec_bustype,
		    r.regspec_addr, r.regspec_size);

		return (fc_priv_error(cp, "gp2 map-in failed"));
	}

	FC_DEBUG1(3, CE_CONT, "gp2_map_in: returning virt %p\n", virt);

	cp->nresults = fc_int2cell(1);
	fc_result(cp, 0) = fc_ptr2cell(virt);

	/*
	 * Log this resource ...
	 */
	ip = kmem_zalloc(sizeof (struct fc_resource), KM_SLEEP);
	ip->type = RT_MAP;
	ip->fc_map_virt = virt;
	ip->fc_map_len = len;
	ip->fc_map_handle = h;
	fc_add_resource(rp, ip);

	return (fc_success_op(ap, rp, cp));
}

/*
 * map-out ( virt size -- )
 */
static int
gfc_map_out(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	caddr_t virt;
	size_t len;
	struct fc_resource *ip;

	if (fc_cell2int(cp->nargs) != 2)
		return (fc_syntax_error(cp, "nargs must be 2"));

	virt = fc_cell2ptr(fc_arg(cp, 1));

	len = fc_cell2size(fc_arg(cp, 0));

	FC_DEBUG2(1, CE_CONT, "gp2_map_out: attempting map out %p %x\n",
	    virt, len);

	/*
	 * Find if this request matches a mapping resource we set up.
	 */
	fc_lock_resource_list(rp);
	for (ip = rp->head; ip != NULL; ip = ip->next) {
		if (ip->type != RT_MAP)
			continue;
		if (ip->fc_map_virt != virt)
			continue;
		if (ip->fc_map_len == len)
			break;
	}
	fc_unlock_resource_list(rp);

	if (ip == NULL)
		return (fc_priv_error(cp, "request doesn't match a "
		    "known mapping"));

	gp2_unmap_phys(&ip->fc_map_handle);

	/*
	 * remove the resource from the list and release it.
	 */
	fc_rem_resource(rp, ip);
	kmem_free(ip, sizeof (struct fc_resource));

	cp->nresults = fc_int2cell(0);
	return (fc_success_op(ap, rp, cp));
}

static int
gfc_register_fetch(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	size_t len;
	caddr_t virt;
	int error = 0;
	uint64_t x;
	uint32_t l;
	uint16_t w;
	uint8_t b;
	char *name = fc_cell2ptr(cp->svc_name);
	struct fc_resource *ip;

	if (fc_cell2int(cp->nargs) != 1)
		return (fc_syntax_error(cp, "nargs must be 1"));

	if (fc_cell2int(cp->nresults) < 1)
		return (fc_syntax_error(cp, "nresults must be >= 1"));

	virt = fc_cell2ptr(fc_arg(cp, 0));

	/*
	 * Determine the access width .. we can switch on the 2nd
	 * character of the name which is "rx@", "rl@", "rb@" or "rw@"
	 */
	switch (*(name + 1)) {
	case 'x':	len = sizeof (x); break;
	case 'l':	len = sizeof (l); break;
	case 'w':	len = sizeof (w); break;
	case 'b':	len = sizeof (b); break;
	}

	/*
	 * Check the alignment ...
	 */
	if (((intptr_t)virt & (len - 1)) != 0)
		return (fc_priv_error(cp, "unaligned access"));

	/*
	 * Find if this virt is 'within' a request we know about
	 */
	fc_lock_resource_list(rp);
	for (ip = rp->head; ip != NULL; ip = ip->next) {
		if (ip->type == RT_MAP) {
		    if ((virt >= (caddr_t)ip->fc_map_virt) && ((virt + len) <=
			((caddr_t)ip->fc_map_virt + ip->fc_map_len)))
				break;
		} else if (ip->type == RT_CONTIGIOUS) {
		    if ((virt >= (caddr_t)ip->fc_contig_virt) && ((virt + len)
			<= ((caddr_t)ip->fc_contig_virt + ip->fc_contig_len)))
				break;
		}
	}
	fc_unlock_resource_list(rp);

	if (ip == NULL) {
		return (fc_priv_error(cp, "request not within a "
		    "known mapping or contigious adddress"));
	}

	switch (len) {
	case sizeof (x):
		if (ip->type == RT_MAP)
		    error = ddi_peek64(rp->child,
			(int64_t *)virt, (int64_t *)&x);
		else /* RT_CONTIGIOUS */
		    x = *(int64_t *)virt;
		break;
	case sizeof (l):
		if (ip->type == RT_MAP)
		    error = ddi_peek32(rp->child,
			(int32_t *)virt, (int32_t *)&l);
		else /* RT_CONTIGIOUS */
		    l = *(int32_t *)virt;
		break;
	case sizeof (w):
		if (ip->type == RT_MAP)
		    error = ddi_peek16(rp->child,
			(int16_t *)virt, (int16_t *)&w);
		else /* RT_CONTIGIOUS */
		    w = *(int16_t *)virt;
		break;
	case sizeof (b):
		if (ip->type == RT_MAP)
		    error = ddi_peek8(rp->child,
			(int8_t *)virt, (int8_t *)&b);
		else /* RT_CONTIGIOUS */
		    b = *(int8_t *)virt;
		break;
	}

	if (error) {
		FC_DEBUG2(1, CE_CONT, "gfc_register_fetch: access error "
		    "accessing virt %p len %d\n", virt, len);
		return (fc_priv_error(cp, "access error"));
	}

	cp->nresults = fc_int2cell(1);
	switch (len) {
	case sizeof (x): fc_result(cp, 0) = x; break;
	case sizeof (l): fc_result(cp, 0) = fc_uint32_t2cell(l); break;
	case sizeof (w): fc_result(cp, 0) = fc_uint16_t2cell(w); break;
	case sizeof (b): fc_result(cp, 0) = fc_uint8_t2cell(b); break;
	}
	return (fc_success_op(ap, rp, cp));
}

static int
gfc_register_store(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	size_t len;
	caddr_t virt;
	uint64_t x;
	uint32_t l;
	uint16_t w;
	uint8_t b;
	char *name = fc_cell2ptr(cp->svc_name);
	struct fc_resource *ip;
	int error = 0;

	if (fc_cell2int(cp->nargs) != 2)
		return (fc_syntax_error(cp, "nargs must be 2"));

	virt = fc_cell2ptr(fc_arg(cp, 0));

	/*
	 * Determine the access width .. we can switch on the 2nd
	 * character of the name which is "rx!", "rl!", "rb!" or "rw!"
	 */
	switch (*(name + 1)) {
	case 'x': len = sizeof (x); x = fc_arg(cp, 1); break;
	case 'l': len = sizeof (l); l = fc_cell2uint32_t(fc_arg(cp, 1)); break;
	case 'w': len = sizeof (w); w = fc_cell2uint16_t(fc_arg(cp, 1)); break;
	case 'b': len = sizeof (b); b = fc_cell2uint8_t(fc_arg(cp, 1)); break;
	}

	/*
	 * Check the alignment ...
	 */
	if (((intptr_t)virt & (len - 1)) != 0)
		return (fc_priv_error(cp, "unaligned access"));

	/*
	 * Find if this virt is 'within' a request we know about
	 */
	fc_lock_resource_list(rp);
	for (ip = rp->head; ip != NULL; ip = ip->next) {
		if (ip->type == RT_MAP) {
		    if ((virt >= (caddr_t)ip->fc_map_virt) && ((virt + len) <=
			((caddr_t)ip->fc_map_virt + ip->fc_map_len)))
				break;
		} else if (ip->type == RT_CONTIGIOUS) {
		    if ((virt >= (caddr_t)ip->fc_contig_virt) && ((virt + len)
			<= ((caddr_t)ip->fc_contig_virt + ip->fc_contig_len)))
				break;
		}
	}
	fc_unlock_resource_list(rp);

	if (ip == NULL)
		return (fc_priv_error(cp, "request not within a "
		    "known mapping or contigious address"));

	switch (len) {
	case sizeof (x):
		if (ip->type == RT_MAP)
			error = ddi_poke64(rp->child, (int64_t *)virt, x);
		else if (ip->type == RT_CONTIGIOUS)
			*(uint64_t *)virt = x;
		break;
	case sizeof (l):
		if (ip->type == RT_MAP)
			error = ddi_poke32(rp->child, (int32_t *)virt, l);
		else if (ip->type == RT_CONTIGIOUS)
			*(uint32_t *)virt = l;
		break;
	case sizeof (w):
		if (ip->type == RT_MAP)
			error = ddi_poke16(rp->child, (int16_t *)virt, w);
		else if (ip->type == RT_CONTIGIOUS)
			*(uint16_t *)virt = w;
		break;
	case sizeof (b):
		if (ip->type == RT_MAP)
			error = ddi_poke8(rp->child, (int8_t *)virt, b);
		else if (ip->type == RT_CONTIGIOUS)
			*(uint8_t *)virt = b;
		break;
	}

	if (error == DDI_FAILURE) {
		FC_DEBUG2(1, CE_CONT, "gfc_register_store: access error "
		    "accessing virt %p len %d\n", virt, len);
		return (fc_priv_error(cp, "access error"));
	}

	cp->nresults = fc_int2cell(0);
	return (fc_success_op(ap, rp, cp));
}

static int
gfc_master_intr(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	int xt, portid;

	if (fc_cell2int(cp->nargs) != 2)
		return (fc_syntax_error(cp, "nargs must be 4"));

	if (fc_cell2int(cp->nresults) < 1)
		return (fc_syntax_error(cp, "nresults must be >= 1"));

	xt = fc_cell2int(fc_arg(cp, 1));
	portid = fc_cell2int(fc_arg(cp, 0));

	FC_DEBUG2(1, CE_CONT, "gfc_master_intr: reset-int-xt=%x portid=%x",
	    xt, portid);

	cp->nresults = fc_int2cell(1);
	fc_result(cp, 0) = 0;

	return (fc_success_op(ap, rp, cp));
}

/*
 * gfc_claim_address
 *
 * claim-address (size.lo size.hi type align bar portid -- base.lo base.hi )
 */
static int
gfc_claim_address(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	int bar, portid;
	uint64_t exp, slot, port, slice;
	uint64_t paddr;

	if (fc_cell2int(cp->nargs) != 6)
		return (fc_syntax_error(cp, "nargs must be 6"));

	if (fc_cell2int(cp->nresults) < 2)
		return (fc_syntax_error(cp, "nresults must be 2"));

	bar = fc_cell2int(fc_arg(cp, 1));
	portid = fc_cell2int(fc_arg(cp, 0));

	exp = portid >> 5;
	slot = (0x8 & portid) >> 3;
	port = portid & 0x1;

	switch (bar) {
	case 0: /* PCI IO Bus A */
		paddr = (exp << 28) | (port << 26) | (slot << 27) |
		    ((uint64_t)0x402 << 32);

		break;
	case 1: /* PCI Memory Bus A */
		slice = (exp * 2) + slot + 1;

		paddr = ((uint64_t)1 << 42) | ((uint64_t)slice << 34) |
		    ((uint64_t)port << 33);

		break;
	case 2: /* PCI IO Bus B */
		paddr = (exp << 28) | (port << 26) | (slot << 27) |
		    ((uint64_t)0x402 << 32)  | (1 << 25);

		break;
	case 3: /* PCI Memory Bus B */
		slice = (exp * 2) + slot + 1;

		paddr = ((uint64_t)1 << 42) | ((uint64_t)slice << 34) |
		    ((uint64_t)port << 33);

		paddr |= ((uint64_t)1 << 32);

		break;
	default:
		cmn_err(CE_WARN,
		    "gfc_claim_address - invalid BAR=0x%x\n", bar);

		return (fc_syntax_error(cp, "invalid argument"));
	}

	FC_DEBUG1(1, CE_CONT, "gfc_claim_address: returning 0x%lx\n", paddr);

	cp->nresults = fc_int2cell(2);
	fc_result(cp, 0) = LOADDR(paddr);
	fc_result(cp, 1) = HIADDR(paddr);

	return (fc_success_op(ap, rp, cp));
}

/*
 * gfc_claim_memory
 *
 * claim-memory ( align size vhint -- vaddr)
 */
static int
gfc_claim_memory(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	int align, size, vhint;
	ndi_ra_request_t request;
	uint64_t answer, alen;
	struct fc_resource *ip;

	if (fc_cell2int(cp->nargs) != 3)
		return (fc_syntax_error(cp, "nargs must be 3"));

	if (fc_cell2int(cp->nresults) < 1)
		return (fc_syntax_error(cp, "nresults must be >= 1"));

	vhint = fc_cell2int(fc_arg(cp, 2));
	size = fc_cell2int(fc_arg(cp, 1));
	align = fc_cell2int(fc_arg(cp, 0));

	FC_DEBUG3(1, CE_CONT, "gfc_claim_memory: align=0x%x size=0x%x "
	    "vhint=0x%x\n", align, size, vhint);

	if (size == 0) {
		cmn_err(CE_WARN, " gfc_claim_memory - unable to allocate "
		    "contigiuos memory of size zero\n");
		return (fc_priv_error(cp, "allocation error"));
	}

	if (vhint) {
		cmn_err(CE_WARN, "gfc_claim_memory - vhint is not zero "
		    "vhint=0x%x - Ignoring Argument\n", vhint);
	}

	bzero((caddr_t)&request, sizeof (ndi_ra_request_t));
	request.ra_flags  = NDI_RA_ALLOC_BOUNDED;
	request.ra_boundbase = 0;
	request.ra_boundlen = 0xffffffff;
	request.ra_len = size;
	request.ra_align_mask = align - 1;

	if (ndi_ra_alloc(ddi_root_node(), &request, &answer, &alen,
	    "gptwo-contigousmem", NDI_RA_PASS) != NDI_SUCCESS) {
		cmn_err(CE_WARN, " gfc_claim_memory - unable to allocate "
		    "contigiuos memory\n");
		return (fc_priv_error(cp, "allocation error"));

	}

	FC_DEBUG2(1, CE_CONT, "gfc_claim_memory: address allocated=0x%lx "
	    "size=0x%x\n", answer, alen);

	cp->nresults = fc_int2cell(1);
	fc_result(cp, 0) = answer;

	/*
	 * Log this resource ...
	 */
	ip = kmem_zalloc(sizeof (struct fc_resource), KM_SLEEP);
	ip->type = RT_CONTIGIOUS;
	ip->fc_contig_virt = (void *)answer;
	ip->fc_contig_len = size;
	fc_add_resource(rp, ip);

	return (fc_success_op(ap, rp, cp));
}

/*
 * gfc_release_memory
 *
 * release-memory ( size vaddr -- )
 */
static int
gfc_release_memory(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	int32_t vaddr, size;
	struct fc_resource *ip;

	if (fc_cell2int(cp->nargs) != 2)
		return (fc_syntax_error(cp, "nargs must be 2"));

	if (fc_cell2int(cp->nresults) != 0)
		return (fc_syntax_error(cp, "nresults must be 0"));

	vaddr = fc_cell2int(fc_arg(cp, 1));
	size = fc_cell2int(fc_arg(cp, 0));

	FC_DEBUG2(1, CE_CONT, "gfc_release_memory: vaddr=0x%x size=0x%x\n",
	    vaddr, size);
	/*
	 * Find if this request matches a mapping resource we set up.
	 */
	fc_lock_resource_list(rp);
	for (ip = rp->head; ip != NULL; ip = ip->next) {
		if (ip->type != RT_CONTIGIOUS)
			continue;
		if (ip->fc_contig_virt != (void *)(uintptr_t)vaddr)
			continue;
		if (ip->fc_contig_len == size)
			break;
	}
	fc_unlock_resource_list(rp);

	if (ip == NULL)
		return (fc_priv_error(cp, "request doesn't match a "
		    "known mapping"));

	(void) ndi_ra_free(ddi_root_node(), vaddr, size,
	    "gptwo-contigousmem", NDI_RA_PASS);

	/*
	 * remove the resource from the list and release it.
	 */
	fc_rem_resource(rp, ip);
	kmem_free(ip, sizeof (struct fc_resource));

	cp->nresults = fc_int2cell(0);

	return (fc_success_op(ap, rp, cp));
}

/*
 * gfc_vtop
 *
 * vtop ( vaddr -- paddr.lo paddr.hi)
 */
static int
gfc_vtop(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	int vaddr;
	uint64_t paddr;
	struct fc_resource *ip;

	if (fc_cell2int(cp->nargs) != 1)
		return (fc_syntax_error(cp, "nargs must be 1"));

	if (fc_cell2int(cp->nresults) >= 3)
		return (fc_syntax_error(cp, "nresults must be less than 2"));

	vaddr = fc_cell2int(fc_arg(cp, 0));

	/*
	 * Find if this request matches a mapping resource we set up.
	 */
	fc_lock_resource_list(rp);
	for (ip = rp->head; ip != NULL; ip = ip->next) {
		if (ip->type != RT_CONTIGIOUS)
			continue;
		if (ip->fc_contig_virt == (void *)(uintptr_t)vaddr)
				break;
	}
	fc_unlock_resource_list(rp);

	if (ip == NULL)
		return (fc_priv_error(cp, "request doesn't match a "
		    "known mapping"));


	paddr = va_to_pa((void *)(uintptr_t)vaddr);

	FC_DEBUG2(1, CE_CONT, "gfc_vtop: vaddr=0x%x paddr=0x%x\n",
	    vaddr, paddr);

	cp->nresults = fc_int2cell(2);

	fc_result(cp, 0) = paddr;
	fc_result(cp, 1) = 0;

	return (fc_success_op(ap, rp, cp));
}

static int
gfc_config_child(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	fc_phandle_t h;

	if (fc_cell2int(cp->nargs) != 0)
		return (fc_syntax_error(cp, "nargs must be 0"));

	if (fc_cell2int(cp->nresults) < 1)
		return (fc_syntax_error(cp, "nresults must be >= 1"));

	h = fc_dip_to_phandle(fc_handle_to_phandle_head(rp), rp->child);

	cp->nresults = fc_int2cell(1);
	fc_result(cp, 0) = fc_phandle2cell(h);

	return (fc_success_op(ap, rp, cp));
}

static int
gfc_get_fcode(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	caddr_t name_virt, fcode_virt;
	char *name, *fcode;
	int fcode_len, status;

	if (fc_cell2int(cp->nargs) != 3)
		return (fc_syntax_error(cp, "nargs must be 3"));

	if (fc_cell2int(cp->nresults) < 1)
		return (fc_syntax_error(cp, "nresults must be >= 1"));

	name_virt = fc_cell2ptr(fc_arg(cp, 0));

	fcode_virt = fc_cell2ptr(fc_arg(cp, 1));

	fcode_len = fc_cell2int(fc_arg(cp, 2));

	name = kmem_zalloc(FC_SVC_NAME_LEN, KM_SLEEP);

	if (copyinstr(fc_cell2ptr(name_virt), name,
	    FC_SVC_NAME_LEN - 1, NULL))  {
		FC_DEBUG1(1, CE_CONT, "gfc_get_fcode: "
		    "fault copying in drop in name %p\n", name_virt);
		status = 0;
	} else {

		fcode = kmem_zalloc(fcode_len, KM_SLEEP);

		if ((status = prom_get_fcode(name, fcode)) != 0) {

			if (copyout((void *)fcode, (void *)fcode_virt,
			    fcode_len)) {
				cmn_err(CE_WARN, " gfc_get_fcode: Unable "
				    "to copy out fcode image\n");
				status = 0;
			}
		}

		kmem_free(fcode, fcode_len);
	}

	kmem_free(name, FC_SVC_NAME_LEN);

	cp->nresults = fc_int2cell(1);
	fc_result(cp, 0) = status;

	return (fc_success_op(ap, rp, cp));
}

static int
gfc_get_fcode_size(dev_info_t *ap, fco_handle_t rp, fc_ci_t *cp)
{
	caddr_t virt;
	char *name;
	int len;

	if (fc_cell2int(cp->nargs) != 1)
		return (fc_syntax_error(cp, "nargs must be 1"));

	if (fc_cell2int(cp->nresults) < 1)
		return (fc_syntax_error(cp, "nresults must be >= 1"));

	virt = fc_cell2ptr(fc_arg(cp, 0));

	name = kmem_zalloc(FC_SVC_NAME_LEN, KM_SLEEP);

	if (copyinstr(fc_cell2ptr(virt), name,
	    FC_SVC_NAME_LEN - 1, NULL))  {
		FC_DEBUG1(1, CE_CONT, "gfc_get_fcode_size: "
		    "fault copying in drop in name %p\n", virt);
		len = 0;
	} else {

		len = prom_get_fcode_size(name);
	}

	kmem_free(name, FC_SVC_NAME_LEN);

	cp->nresults = fc_int2cell(1);
	fc_result(cp, 0) = len;

	return (fc_success_op(ap, rp, cp));
}

static int
gp2_map_phys(dev_info_t *dip, struct regspec *phys_spec,
	caddr_t *addrp, ddi_device_acc_attr_t *accattrp,
	ddi_acc_handle_t *handlep)
{
	ddi_map_req_t mr;
	ddi_acc_hdl_t *hp;
	int result;
	struct regspec *ph;

	*handlep = impl_acc_hdl_alloc(KM_SLEEP, NULL);
	hp = impl_acc_hdl_get(*handlep);
	hp->ah_vers = VERS_ACCHDL;
	hp->ah_dip = dip;
	hp->ah_rnumber = 0;
	hp->ah_offset = 0;
	hp->ah_len = 0;
	hp->ah_acc = *accattrp;
	ph = kmem_zalloc(sizeof (struct regspec), KM_SLEEP);
	*ph = *phys_spec;
	hp->ah_bus_private = ph;	/* cache a copy of the reg spec */

	mr.map_op = DDI_MO_MAP_LOCKED;
	mr.map_type = DDI_MT_REGSPEC;
	mr.map_obj.rp = (struct regspec *)phys_spec;
	mr.map_prot = PROT_READ | PROT_WRITE;
	mr.map_flags = DDI_MF_KERNEL_MAPPING;
	mr.map_handlep = hp;
	mr.map_vers = DDI_MAP_VERSION;

	result = ddi_map(dip, &mr, 0, 0, addrp);

	if (result != DDI_SUCCESS) {
		impl_acc_hdl_free(*handlep);
		*handlep = (ddi_acc_handle_t)NULL;
	} else {
		hp->ah_addr = *addrp;
	}

	return (result);
}

static void
gp2_unmap_phys(ddi_acc_handle_t *handlep)
{
	ddi_map_req_t mr;
	ddi_acc_hdl_t *hp;
	struct regspec_t *ph;

	hp = impl_acc_hdl_get(*handlep);
	ASSERT(hp);
	ph = hp->ah_bus_private;

	mr.map_op = DDI_MO_UNMAP;
	mr.map_type = DDI_MT_REGSPEC;
	mr.map_obj.rp = (struct regspec *)ph;
	mr.map_prot = PROT_READ | PROT_WRITE;
	mr.map_flags = DDI_MF_KERNEL_MAPPING;
	mr.map_handlep = hp;
	mr.map_vers = DDI_MAP_VERSION;

	(void) ddi_map(hp->ah_dip, &mr, hp->ah_offset,
		hp->ah_len, &hp->ah_addr);

	impl_acc_hdl_free(*handlep);
	kmem_free(ph, sizeof (struct regspec));	/* Free the cached copy */
	*handlep = (ddi_acc_handle_t)NULL;
}