/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/param.h>

#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/promif.h>
#include <sys/cpuvar.h>

#include <sys/pci/pci_obj.h>
#include <sys/hotplug/pci/pcihp.h>

#include <sys/pci_tools.h>
#include <sys/pci/pci_tools_ext.h>

/*
 * Number of interrupts supported per PCI bus.
 */
#define	PCI_MAX_INO		0x3f

/*
 * PCI Space definitions.
 */
#define	PCI_CONFIG_RANGE_BANK	(PCI_REG_ADDR_G(PCI_ADDR_CONFIG))
#define	PCI_IO_RANGE_BANK	(PCI_REG_ADDR_G(PCI_ADDR_IO))
#define	PCI_MEM_RANGE_BANK	(PCI_REG_ADDR_G(PCI_ADDR_MEM32))
#define	PCI_MEM64_RANGE_BANK	(PCI_REG_ADDR_G(PCI_ADDR_MEM64))

/*
 * Extract 64 bit parent or size values from 32 bit cells of
 * pci_ranges_t property.
 *
 * Only bits 42-32 are relevant in parent_high.
 */
#define	PCI_GET_RANGE_PROP(ranges, bank) \
	((((uint64_t)(ranges[bank].parent_high & 0x7ff)) << 32) | \
	ranges[bank].parent_low)

#define	PCI_GET_RANGE_PROP_SIZE(ranges, bank) \
	((((uint64_t)(ranges[bank].size_high)) << 32) | \
	ranges[bank].size_low)

#define	PCI_BAR_OFFSET(x)	(pci_bars[x.barnum])

/* Big and little endian as boolean values. */
#define	BE B_TRUE
#define	LE B_FALSE

#define	SUCCESS	0

/* Mechanism for getting offsets of smaller datatypes aligned in 64 bit long */
typedef union {
	uint64_t u64;
	uint32_t u32;
	uint16_t u16;
	uint8_t u8;
} peek_poke_value_t;

/*
 * Offsets of BARS in config space.  First entry of 0 means config space.
 * Entries here correlate to pcitool_bars_t enumerated type.
 */
static uint8_t pci_bars[] = {
	0x0,
	PCI_CONF_BASE0,
	PCI_CONF_BASE1,
	PCI_CONF_BASE2,
	PCI_CONF_BASE3,
	PCI_CONF_BASE4,
	PCI_CONF_BASE5,
	PCI_CONF_ROM
};

/*LINTLIBRARY*/

static int pcitool_phys_peek(pci_t *pci_p, boolean_t type, size_t size,
    uint64_t paddr, uint64_t *value_p);
static int pcitool_phys_poke(pci_t *pci_p, boolean_t type, size_t size,
    uint64_t paddr, uint64_t value);
static int pcitool_access(pci_t *pci_p, uint64_t phys_addr, uint64_t max_addr,
    uint64_t *data, uint8_t size, boolean_t write, boolean_t endian,
    uint32_t *pcitool_status);
static int pcitool_validate_barnum_bdf(pcitool_reg_t *prg);
static int pcitool_get_bar(pci_t *pci_p, pcitool_reg_t *prg,
    uint64_t config_base_addr, uint64_t config_max_addr, uint64_t *bar,
    boolean_t *is_io_space);
static int pcitool_config_request(pci_t *pci_p, pcitool_reg_t *prg,
    uint64_t base_addr, uint64_t max_addr, uint8_t size, boolean_t write_flag);
static int pcitool_intr_get_max_ino(uint32_t *arg, int mode);
static int pcitool_get_intr(dev_info_t *dip, void *arg, int mode, pci_t *pci_p);
static int pcitool_set_intr(dev_info_t *dip, void *arg, int mode, pci_t *pci_p);

extern int pci_do_phys_peek(size_t, uint64_t, uint64_t *, int);
extern int pci_do_phys_poke(size_t, uint64_t, uint64_t *, int);

/*
 * Safe C wrapper around assy language routine pci_do_phys_peek
 *
 * Type is TRUE for big endian, FALSE for little endian.
 * Size is 1, 2, 4 or 8 bytes.
 * paddr is the physical address in IO space to access read.
 * value_p is where the value is returned.
 */
static int
pcitool_phys_peek(pci_t *pci_p, boolean_t type, size_t size,
	uint64_t paddr, uint64_t *value_p)
{
	on_trap_data_t otd;
	int err = DDI_SUCCESS;
	peek_poke_value_t peek_value;

	pbm_t *pbm_p = pci_p->pci_pbm_p;

	pbm_p->pbm_ontrap_data = &otd;

	/* Set up trap handling to make the access safe. */

	/*
	 * on_trap works like setjmp.
	 * Set it up to not panic on data access error,
	 * but to call peek_fault instead.
	 * Call pci_do_phys_peek after trap handling is setup.
	 * When on_trap returns FALSE, it has been setup.
	 * When it returns TRUE, an it has caught an error.
	 */
	if (!on_trap(&otd, OT_DATA_ACCESS)) {
		otd.ot_trampoline = (uintptr_t)&peek_fault;
		err = pci_do_phys_peek(size, paddr, &peek_value.u64, type);
	} else {
		err = DDI_FAILURE;
	}

	pbm_p->pbm_ontrap_data = NULL;
	no_trap();

	if (err != DDI_FAILURE) {
		switch (size) {
		case 8:
			*value_p = (uint64_t)peek_value.u64;
			break;
		case 4:
			*value_p = (uint64_t)peek_value.u32;
			break;
		case 2:
			*value_p = (uint64_t)peek_value.u16;
			break;
		case 1:
			*value_p = (uint64_t)peek_value.u8;
			break;
		default:
			err = DDI_FAILURE;
		}
	}

	return (err);
}

/*
 * Safe C wrapper around assy language routine pci_do_phys_poke
 *
 * Type is TRUE for big endian, FALSE for little endian.
 * Size is 1,2,4 or 8 bytes.
 * paddr is the physical address in IO space to access read.
 * value contains the value to be written.
 */
static int
pcitool_phys_poke(pci_t *pci_p, boolean_t type, size_t size,
	uint64_t paddr, uint64_t value)
{
	on_trap_data_t otd;
	int err = DDI_SUCCESS;
	peek_poke_value_t poke_value;

	pbm_t *pbm_p = pci_p->pci_pbm_p;

	switch (size) {
	case 8:
		poke_value.u64 = value;
		break;
	case 4:
		poke_value.u32 = (uint32_t)value;
		break;
	case 2:
		poke_value.u16 = (uint16_t)value;
		break;
	case 1:
		poke_value.u8 = (uint8_t)value;
		break;
	default:
		return (DDI_FAILURE);
	}

	mutex_enter(&pbm_p->pbm_pokefault_mutex);

	pbm_p->pbm_ontrap_data = &otd;

	/*
	 * on_trap works like setjmp.
	 * Set it up to not panic on data access error,
	 * but to call poke_fault instead.
	 * Call pci_do_phys_poke after trap handling is setup.
	 * When on_trap returns FALSE, it has been setup.
	 * When it returns TRUE, an it has caught an error.
	 */
	if (!on_trap(&otd, OT_DATA_ACCESS)) {
		otd.ot_trampoline = (uintptr_t)&poke_fault;
		err = pci_do_phys_poke(size, paddr, &poke_value.u64, type);
	}

	/* Let the dust settle and errors occur if they will. */
	pbm_clear_error(pbm_p);

	/* Check for an error. */
	if (otd.ot_trap == OT_DATA_ACCESS) {
		err = DDI_FAILURE;
	}

	pbm_p->pbm_ontrap_data = NULL;
	mutex_exit(&pbm_p->pbm_pokefault_mutex);

	no_trap();
	return (err);
}


/*ARGSUSED*/
static int
pcitool_intr_info(dev_info_t *dip, void *arg, int mode)
{
	pcitool_intr_info_t intr_info;
	int rval = SUCCESS;

	/* If we need user_version, and to ret same user version as passed in */
	if (ddi_copyin(arg, &intr_info, sizeof (pcitool_intr_info_t), mode) !=
	    DDI_SUCCESS) {
		return (EFAULT);
	}

	if (intr_info.flags & PCITOOL_INTR_FLAG_GET_MSI)
		return (ENOTSUP);

	intr_info.ctlr_version = 0;	/* XXX how to get real version? */
	intr_info.ctlr_type = PCITOOL_CTLR_TYPE_RISC;
	intr_info.num_intr = PCI_MAX_INO;

	intr_info.drvr_version = PCITOOL_VERSION;
	if (ddi_copyout(&intr_info, arg, sizeof (pcitool_intr_info_t), mode) !=
	    DDI_SUCCESS) {
		rval = EFAULT;
	}

	return (rval);
}


/*
 * Get interrupt information for a given ino.
 * Returns info only for inos mapped to devices.
 *
 * Returned info is valid only when iget.num_devs is returned > 0.
 * If ino is not enabled or is not mapped to a device, num_devs will be = 0.
 */
/*ARGSUSED*/
static int
pcitool_get_intr(dev_info_t *dip, void *arg, int mode, pci_t *pci_p)
{
	/* Array part isn't used here, but oh well... */
	pcitool_intr_get_t partial_iget;
	pcitool_intr_get_t *iget = &partial_iget;
	size_t	iget_kmem_alloc_size = 0;
	ib_t *ib_p = pci_p->pci_ib_p;
	volatile uint64_t *imregp;
	uint64_t imregval;
	uint32_t ino;
	uint8_t num_devs_ret;
	int cpu_id;
	int copyout_rval;
	int rval = SUCCESS;

	/* Read in just the header part, no array section. */
	if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
	    DDI_SUCCESS) {

		return (EFAULT);
	}

	if (partial_iget.flags & PCITOOL_INTR_FLAG_GET_MSI) {
		partial_iget.status = PCITOOL_IO_ERROR;
		partial_iget.num_devs_ret = 0;
		rval = ENOTSUP;
		goto done_get_intr;
	}

	ino = partial_iget.ino;
	num_devs_ret = partial_iget.num_devs_ret;

	/* Validate argument. */
	if (ino > PCI_MAX_INO) {
		partial_iget.status = PCITOOL_INVALID_INO;
		partial_iget.num_devs_ret = 0;
		rval = EINVAL;
		goto done_get_intr;
	}

	/* Caller wants device information returned. */
	if (num_devs_ret > 0) {

		/*
		 * Allocate room.
		 * Note if num_devs_ret == 0 iget remains pointing to
		 * partial_iget.
		 */
		iget_kmem_alloc_size = PCITOOL_IGET_SIZE(num_devs_ret);
		iget = kmem_alloc(iget_kmem_alloc_size, KM_SLEEP);

		/* Read in whole structure to verify there's room. */
		if (ddi_copyin(arg, iget, iget_kmem_alloc_size, mode) !=
		    SUCCESS) {

			/* Be consistent and just return EFAULT here. */
			kmem_free(iget, iget_kmem_alloc_size);

			return (EFAULT);
		}
	}

	bzero(iget, PCITOOL_IGET_SIZE(num_devs_ret));
	iget->ino = ino;
	iget->num_devs_ret = num_devs_ret;

	imregp = ib_intr_map_reg_addr(ib_p, ino);
	imregval = *imregp;

	/*
	 * Read "valid" bit.  If set, interrupts are enabled.
	 * This bit happens to be the same on Fire and Tomatillo.
	 */
	if (imregval & COMMON_INTR_MAP_REG_VALID) {
		/*
		 * The following looks up the ib_ino_info and returns
		 * info of devices mapped to this ino.
		 */
		iget->num_devs = ib_get_ino_devs(
		    ib_p, ino, &iget->num_devs_ret, iget->dev);

		if (ib_get_intr_target(pci_p, ino, &cpu_id) != DDI_SUCCESS) {
			iget->status = PCITOOL_IO_ERROR;
			rval = EIO;
			goto done_get_intr;
		}

		/*
		 * Consider only inos mapped to devices (as opposed to
		 * inos mapped to the bridge itself.
		 */
		if (iget->num_devs > 0) {
			/*
			 * These 2 items are platform specific,
			 * extracted from the bridge.
			 */
			iget->ctlr = 0;
			iget->cpu_id = cpu_id;
		}
	}
done_get_intr:
	iget->drvr_version = PCITOOL_VERSION;
	copyout_rval = ddi_copyout(iget, arg,
	    PCITOOL_IGET_SIZE(num_devs_ret), mode);

	if (iget_kmem_alloc_size > 0) {
		kmem_free(iget, iget_kmem_alloc_size);
	}

	if (copyout_rval != DDI_SUCCESS) {
		rval = EFAULT;
	}

	return (rval);
}

/*
 * Associate a new CPU with a given ino.
 *
 * Operate only on inos which are already mapped to devices.
 */
static int
pcitool_set_intr(dev_info_t *dip, void *arg, int mode, pci_t *pci_p)
{
	ib_t *ib_p = pci_p->pci_ib_p;
	int rval = SUCCESS;
	int ret = DDI_SUCCESS;
	uint8_t zero = 0;
	pcitool_intr_set_t iset;
	volatile uint64_t *imregp;
	uint64_t imregval;

	size_t copyinout_size;
	int old_cpu_id;

	bzero(&iset, sizeof (pcitool_intr_set_t));

	/* Version 1 of pcitool_intr_set_t doesn't have flags. */
	copyinout_size = (size_t)&iset.flags - (size_t)&iset;

	if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
		return (EFAULT);

	switch (iset.user_version) {
	case PCITOOL_V1:
		break;

	case PCITOOL_V2:
		copyinout_size = sizeof (pcitool_intr_set_t);
		if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
			return (EFAULT);
		break;

	default:
		iset.status = PCITOOL_OUT_OF_RANGE;
		rval = ENOTSUP;
		goto done_set_intr;
	}

	if ((iset.flags & PCITOOL_INTR_FLAG_SET_GROUP) ||
	    (iset.flags & PCITOOL_INTR_FLAG_SET_MSI)) {
		iset.status = PCITOOL_IO_ERROR;
		rval = ENOTSUP;
		goto done_set_intr;
	}

	/* Validate input argument and that ino given belongs to a device. */
	if ((iset.ino > PCI_MAX_INO) ||
	    (ib_get_ino_devs(ib_p, iset.ino, &zero, NULL) == 0)) {
		iset.status = PCITOOL_INVALID_INO;
		rval = EINVAL;
		goto done_set_intr;
	}

	imregp = (uint64_t *)ib_intr_map_reg_addr(ib_p, iset.ino);
	imregval = *imregp;

	/* Operate only on inos which are already enabled. */
	if (!(imregval & COMMON_INTR_MAP_REG_VALID)) {
		iset.status = PCITOOL_INVALID_INO;
		rval = EINVAL;
		goto done_set_intr;
	}

	if (ib_get_intr_target(pci_p, iset.ino, &old_cpu_id) != DDI_SUCCESS) {
		iset.status = PCITOOL_INVALID_INO;
		rval = EINVAL;
		goto done_set_intr;
	}

	if ((ret = ib_set_intr_target(pci_p, iset.ino,
	    iset.cpu_id)) == DDI_SUCCESS) {
		iset.cpu_id = old_cpu_id;
		iset.status = PCITOOL_SUCCESS;
		goto done_set_intr;
	}

	switch (ret) {
	case DDI_EPENDING:
		iset.status = PCITOOL_PENDING_INTRTIMEOUT;
		rval = ETIME;
		break;
	case DDI_EINVAL:
		iset.status = PCITOOL_INVALID_CPUID;
		rval = EINVAL;
		break;
	default:
		iset.status = PCITOOL_INVALID_INO;
		rval = EINVAL;
		break;
	}
done_set_intr:
	iset.drvr_version = PCITOOL_VERSION;
	if (ddi_copyout(&iset, arg, copyinout_size, mode) != DDI_SUCCESS)
		rval = EFAULT;

	return (rval);
}


/* Main function for handling interrupt CPU binding requests and queries. */
int
pcitool_intr_admn(dev_t dev, void *arg, int cmd, int mode)
{
	pci_t		*pci_p = DEV_TO_SOFTSTATE(dev);
	dev_info_t	*dip = pci_p->pci_dip;
	int		rval = SUCCESS;

	switch (cmd) {

	/* Get system interrupt information. */
	case PCITOOL_SYSTEM_INTR_INFO:
		rval = pcitool_intr_info(dip, arg, mode);
		break;

	/* Get interrupt information for a given ino. */
	case PCITOOL_DEVICE_GET_INTR:
		rval = pcitool_get_intr(dip, arg, mode, pci_p);
		break;

	/* Associate a new CPU with a given ino. */
	case PCITOOL_DEVICE_SET_INTR:
		rval = pcitool_set_intr(dip, arg, mode, pci_p);
		break;

	default:
		rval = ENOTTY;
	}

	return (rval);
}


/*
 * Wrapper around pcitool_phys_peek/poke.
 *
 * Validates arguments and calls pcitool_phys_peek/poke appropriately.
 *
 * Dip is of the nexus,
 * phys_addr is the address to write in physical space,
 * max_addr is the upper bound on the physical space used for bounds checking,
 * pcitool_status returns more detailed status in addition to a more generic
 * errno-style function return value.
 * other args are self-explanatory.
 */
static int
pcitool_access(pci_t *pci_p, uint64_t phys_addr, uint64_t max_addr,
	uint64_t *data, uint8_t size, boolean_t write, boolean_t endian,
	uint32_t *pcitool_status)
{

	int rval = SUCCESS;
	dev_info_t *dip = pci_p->pci_dip;

	/* Upper bounds checking. */
	if (phys_addr > max_addr) {
		DEBUG2(DBG_TOOLS, dip,
		    "Phys addr 0x%llx out of range (max 0x%llx).\n",
		    phys_addr, max_addr);
		*pcitool_status = PCITOOL_INVALID_ADDRESS;

		rval = EINVAL;

	/* Alignment checking. */
	} else if (!IS_P2ALIGNED(phys_addr, size)) {
		DEBUG0(DBG_TOOLS, dip, "not aligned.\n");
		*pcitool_status = PCITOOL_NOT_ALIGNED;

		rval = EINVAL;

	/* Made it through checks.  Do the access. */
	} else if (write) {

		DEBUG3(DBG_PHYS_ACC, dip,
		    "%d byte %s pcitool_phys_poke at addr 0x%llx\n",
		    size, (endian ? "BE" : "LE"), phys_addr);

		if (pcitool_phys_poke(pci_p, endian, size, phys_addr,
		    *data) != DDI_SUCCESS) {
			DEBUG3(DBG_PHYS_ACC, dip,
			    "%d byte %s pcitool_phys_poke at addr "
			    "0x%llx failed\n",
			    size, (endian ? "BE" : "LE"), phys_addr);
			*pcitool_status = PCITOOL_INVALID_ADDRESS;

			rval = EFAULT;
		}

	} else {	/* Read */

		DEBUG3(DBG_PHYS_ACC, dip,
		    "%d byte %s pcitool_phys_peek at addr 0x%llx\n",
		    size, (endian ? "BE" : "LE"), phys_addr);

		if (pcitool_phys_peek(pci_p, endian, size, phys_addr,
		    data) != DDI_SUCCESS) {
			DEBUG3(DBG_PHYS_ACC, dip,
			    "%d byte %s pcitool_phys_peek at addr "
			    "0x%llx failed\n",
			    size, (endian ? "BE" : "LE"), phys_addr);
			*pcitool_status = PCITOOL_INVALID_ADDRESS;

			rval = EFAULT;
		}
	}
	return (rval);
}

/*
 * Perform register accesses on the nexus device itself.
 */
int
pcitool_bus_reg_ops(dev_t dev, void *arg, int cmd, int mode)
{

	pci_t			*pci_p = DEV_TO_SOFTSTATE(dev);
	dev_info_t		*dip = pci_p->pci_dip;
	pci_nexus_regspec_t	*pci_rp = NULL;
	boolean_t		write_flag = B_FALSE;
	pcitool_reg_t		prg;
	uint64_t		base_addr;
	uint64_t		max_addr;
	uint32_t		reglen;
	uint8_t			size;
	uint32_t		rval = 0;

	if (cmd == PCITOOL_NEXUS_SET_REG)
		write_flag = B_TRUE;

	DEBUG0(DBG_TOOLS, dip, "pcitool_bus_reg_ops nexus set/get reg\n");

	/* Read data from userland. */
	if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
	    DDI_SUCCESS) {
		DEBUG0(DBG_TOOLS, dip, "Error reading arguments\n");
		return (EFAULT);
	}

	/* Read reg property which contains starting addr and size of banks. */
	if (ddi_prop_lookup_int_array(
	    DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
	    "reg", (int **)&pci_rp, &reglen) == DDI_SUCCESS) {
		if (((reglen * sizeof (int)) %
		    sizeof (pci_nexus_regspec_t)) != 0) {
			DEBUG0(DBG_TOOLS, dip, "reg prop not well-formed");
			prg.status = PCITOOL_REGPROP_NOTWELLFORMED;
			rval = EIO;
			goto done;
		}
	}

	/* Bounds check the bank number. */
	if (prg.barnum >=
	    (reglen * sizeof (int)) / sizeof (pci_nexus_regspec_t)) {
		prg.status = PCITOOL_OUT_OF_RANGE;
		rval = EINVAL;
		goto done;
	}

	size = PCITOOL_ACC_ATTR_SIZE(prg.acc_attr);
	base_addr = pci_rp[prg.barnum].phys_addr;
	max_addr = base_addr + pci_rp[prg.barnum].size;
	prg.phys_addr = base_addr + prg.offset;

	DEBUG4(DBG_TOOLS, dip,
	    "pcitool_bus_reg_ops: nexus: base:0x%llx, offset:0x%llx, "
	    "addr:0x%llx, max_addr:0x%llx\n",
	    base_addr, prg.offset, prg.phys_addr, max_addr);

	/* Access device.  prg.status is modified. */
	rval = pcitool_access(pci_p,
	    prg.phys_addr, max_addr, &prg.data, size, write_flag,
	    PCITOOL_ACC_IS_BIG_ENDIAN(prg.acc_attr), &prg.status);

done:
	if (pci_rp != NULL)
		ddi_prop_free(pci_rp);

	prg.drvr_version = PCITOOL_VERSION;
	if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) !=
	    DDI_SUCCESS) {
		DEBUG0(DBG_TOOLS, dip, "Copyout failed.\n");
		return (EFAULT);
	}

	return (rval);
}


static int
pcitool_validate_barnum_bdf(pcitool_reg_t *prg)
{
	int rval = SUCCESS;

	if (prg->barnum >= (sizeof (pci_bars) / sizeof (pci_bars[0]))) {
		prg->status = PCITOOL_OUT_OF_RANGE;
		rval = EINVAL;

	/* Validate address arguments of bus / dev / func */
	} else if (((prg->bus_no &
	    (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) != prg->bus_no) ||
	    ((prg->dev_no &
	    (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) != prg->dev_no) ||
	    ((prg->func_no &
	    (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) != prg->func_no)) {
		prg->status = PCITOOL_INVALID_ADDRESS;
		rval = EINVAL;
	}

	return (rval);
}

static int
pcitool_get_bar(pci_t *pci_p, pcitool_reg_t *prg, uint64_t config_base_addr,
	uint64_t config_max_addr, uint64_t *bar, boolean_t *is_io_space)
{

	uint8_t bar_offset;
	int rval;
	dev_info_t *dip = pci_p->pci_dip;

	*bar = 0;
	*is_io_space = B_FALSE;

	/*
	 * Translate BAR number into offset of the BAR in
	 * the device's config space.
	 */
	bar_offset = PCI_BAR_OFFSET((*prg));

	DEBUG2(DBG_TOOLS, dip, "barnum:%d, bar_offset:0x%x\n",
	    prg->barnum, bar_offset);

	/*
	 * Get Bus Address Register (BAR) from config space.
	 * bar_offset is the offset into config space of the BAR desired.
	 * prg->status is modified on error.
	 */
	rval = pcitool_access(pci_p, config_base_addr + bar_offset,
	    config_max_addr, bar,
	    4,		/* 4 bytes. */
	    B_FALSE,	/* Read */
	    B_FALSE, 	/* Little endian. */
	    &prg->status);
	if (rval != SUCCESS)
		return (rval);

	DEBUG1(DBG_TOOLS, dip, "bar returned is 0x%llx\n", *bar);
	if (!(*bar)) {
		prg->status = PCITOOL_INVALID_ADDRESS;
		return (EINVAL);
	}

	/*
	 * BAR has bits saying this space is IO space, unless
	 * this is the ROM address register.
	 */
	if (((PCI_BASE_SPACE_M & *bar) == PCI_BASE_SPACE_IO) &&
	    (bar_offset != PCI_CONF_ROM)) {
		*is_io_space = B_TRUE;
		*bar &= PCI_BASE_IO_ADDR_M;

	/*
	 * BAR has bits saying this space is 64 bit memory
	 * space, unless this is the ROM address register.
	 *
	 * The 64 bit address stored in two BAR cells is not necessarily
	 * aligned on an 8-byte boundary.  Need to keep the first 4
	 * bytes read, and do a separate read of the high 4 bytes.
	 */

	} else if ((PCI_BASE_TYPE_ALL & *bar) && (bar_offset != PCI_CONF_ROM)) {

		uint32_t low_bytes = (uint32_t)(*bar & ~PCI_BASE_TYPE_ALL);

		/* Don't try to read past the end of BARs. */
		if (bar_offset >= PCI_CONF_BASE5) {
			prg->status = PCITOOL_OUT_OF_RANGE;
			return (EIO);
		}

		/* Access device.  prg->status is modified on error. */
		rval = pcitool_access(pci_p,
		    config_base_addr + bar_offset + 4, config_max_addr, bar,
		    4,		/* 4 bytes. */
		    B_FALSE,	/* Read */
		    B_FALSE, 	/* Little endian. */
		    &prg->status);
		if (rval != SUCCESS)
			return (rval);

		*bar = (*bar << 32) + low_bytes;
	}

	return (SUCCESS);
}


static int
pcitool_config_request(pci_t *pci_p, pcitool_reg_t *prg, uint64_t base_addr,
	uint64_t max_addr, uint8_t size, boolean_t write_flag)
{
	int rval;
	dev_info_t *dip = pci_p->pci_dip;

	/* Access config space and we're done. */
	prg->phys_addr = base_addr + prg->offset;

	DEBUG4(DBG_TOOLS, dip, "config access: base:0x%llx, "
	    "offset:0x%llx, phys_addr:0x%llx, end:%s\n",
	    base_addr, prg->offset, prg->phys_addr,
	    (PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr)? "big" : "ltl"));

	/* Access device.  pr.status is modified. */
	rval = pcitool_access(pci_p, prg->phys_addr, max_addr, &prg->data, size,
	    write_flag, PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr), &prg->status);

	DEBUG1(DBG_TOOLS, dip, "config access: data:0x%llx\n", prg->data);

	return (rval);
}

/* Perform register accesses on PCI leaf devices. */
int
pcitool_dev_reg_ops(dev_t dev, void *arg, int cmd, int mode)
{
	pci_t		*pci_p = DEV_TO_SOFTSTATE(dev);
	dev_info_t	*dip = pci_p->pci_dip;
	pci_ranges_t	*rp = pci_p->pci_ranges;
	pcitool_reg_t	prg;
	uint64_t	max_addr;
	uint64_t	base_addr;
	uint64_t	range_prop;
	uint64_t	range_prop_size;
	uint64_t	bar = 0;
	int		rval = 0;
	boolean_t	write_flag = B_FALSE;
	boolean_t	is_io_space = B_FALSE;
	uint8_t		size;

	if (cmd == PCITOOL_DEVICE_SET_REG)
		write_flag = B_TRUE;

	DEBUG0(DBG_TOOLS, dip, "pcitool_dev_reg_ops set/get reg\n");
	if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
	    DDI_SUCCESS) {
		DEBUG0(DBG_TOOLS, dip, "Error reading arguments\n");
		return (EFAULT);
	}

	DEBUG3(DBG_TOOLS, dip, "raw bus:0x%x, dev:0x%x, func:0x%x\n",
	    prg.bus_no, prg.dev_no, prg.func_no);

	if ((rval = pcitool_validate_barnum_bdf(&prg)) != SUCCESS)
		goto done_reg;

	size = PCITOOL_ACC_ATTR_SIZE(prg.acc_attr);

	/* Get config space first. */
	range_prop = PCI_GET_RANGE_PROP(rp, PCI_CONFIG_RANGE_BANK);
	range_prop_size = PCI_GET_RANGE_PROP_SIZE(rp, PCI_CONFIG_RANGE_BANK);
	max_addr = range_prop + range_prop_size;

	/*
	 * Build device address based on base addr from range prop, and bus,
	 * dev and func values passed in.  This address is where config space
	 * begins.
	 */
	base_addr = range_prop +
	    (prg.bus_no << PCI_REG_BUS_SHIFT) +
	    (prg.dev_no << PCI_REG_DEV_SHIFT) +
	    (prg.func_no << PCI_REG_FUNC_SHIFT);

	if ((base_addr < range_prop) || (base_addr >= max_addr)) {
		prg.status = PCITOOL_OUT_OF_RANGE;
		rval = EINVAL;
		goto done_reg;
	}

	DEBUG5(DBG_TOOLS, dip, "range_prop:0x%llx, shifted: bus:0x%x, dev:0x%x "
	    "func:0x%x, addr:0x%x\n", range_prop,
	    prg.bus_no << PCI_REG_BUS_SHIFT, prg.dev_no << PCI_REG_DEV_SHIFT,
	    prg.func_no << PCI_REG_FUNC_SHIFT, base_addr);

	/* Proper config space desired. */
	if (prg.barnum == 0) {

		rval = pcitool_config_request(pci_p, &prg, base_addr, max_addr,
		    size, write_flag);

	} else {	/* IO / MEM / MEM64 space. */

		if (pcitool_get_bar(pci_p, &prg, base_addr, max_addr, &bar,
		    &is_io_space) != SUCCESS)
			goto done_reg;

		/* IO space. */
		if (is_io_space) {

			DEBUG0(DBG_TOOLS, dip, "IO space\n");

			/* Reposition to focus on IO space. */
			range_prop = PCI_GET_RANGE_PROP(rp, PCI_IO_RANGE_BANK);
			range_prop_size = PCI_GET_RANGE_PROP_SIZE(rp,
			    PCI_IO_RANGE_BANK);

		/* 64 bit memory space. */
		} else if ((bar >> 32) != 0) {

			DEBUG1(DBG_TOOLS, dip,
			    "64 bit mem space.  64-bit bar is 0x%llx\n", bar);

			/* Reposition to MEM64 range space. */
			range_prop = PCI_GET_RANGE_PROP(rp,
			    PCI_MEM64_RANGE_BANK);
			range_prop_size = PCI_GET_RANGE_PROP_SIZE(rp,
			    PCI_MEM64_RANGE_BANK);

		} else {	/* Mem32 space, including ROM */

			DEBUG0(DBG_TOOLS, dip, "32 bit mem space\n");

			if (PCI_BAR_OFFSET(prg) == PCI_CONF_ROM) {

				DEBUG0(DBG_TOOLS, dip,
				    "Additional ROM checking\n");

				/* Can't write to ROM */
				if (write_flag) {
					prg.status = PCITOOL_ROM_WRITE;
					rval = EIO;
					goto done_reg;

				/* ROM disabled for reading */
				} else if (!(bar & 0x00000001)) {
					prg.status = PCITOOL_ROM_DISABLED;
					rval = EIO;
					goto done_reg;
				}
			}

			range_prop = PCI_GET_RANGE_PROP(rp, PCI_MEM_RANGE_BANK);
			range_prop_size = PCI_GET_RANGE_PROP_SIZE(rp,
			    PCI_MEM_RANGE_BANK);
		}

		/* Common code for all IO/MEM range spaces. */
		max_addr = range_prop + range_prop_size;
		base_addr = range_prop + bar;

		DEBUG3(DBG_TOOLS, dip,
		    "addr portion of bar is 0x%llx, base=0x%llx, "
		    "offset:0x%lx\n", bar, base_addr, prg.offset);

		/*
		 * Use offset provided by caller to index into
		 * desired space, then access.
		 * Note that prg.status is modified on error.
		 */
		prg.phys_addr = base_addr + prg.offset;
		rval = pcitool_access(pci_p, prg.phys_addr,
		    max_addr, &prg.data, size, write_flag,
		    PCITOOL_ACC_IS_BIG_ENDIAN(prg.acc_attr), &prg.status);
	}

done_reg:
	prg.drvr_version = PCITOOL_VERSION;
	if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) !=
	    DDI_SUCCESS) {
		DEBUG0(DBG_TOOLS, dip, "Error returning arguments.\n");
		rval = EFAULT;
	}
	return (rval);
}

int
pcitool_init(dev_info_t *dip)
{
	int instance = ddi_get_instance(dip);

	if (ddi_create_minor_node(dip, PCI_MINOR_REG, S_IFCHR,
	    PCIHP_AP_MINOR_NUM(instance, PCI_TOOL_REG_MINOR_NUM),
	    DDI_NT_REGACC, 0) != DDI_SUCCESS)
		return (DDI_FAILURE);

	if (ddi_create_minor_node(dip, PCI_MINOR_INTR, S_IFCHR,
	    PCIHP_AP_MINOR_NUM(instance, PCI_TOOL_INTR_MINOR_NUM),
	    DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
		ddi_remove_minor_node(dip, PCI_MINOR_REG);
		return (DDI_FAILURE);
	}

	return (DDI_SUCCESS);
}

void
pcitool_uninit(dev_info_t *dip)
{
	ddi_remove_minor_node(dip, PCI_MINOR_REG);
	ddi_remove_minor_node(dip, PCI_MINOR_INTR);
}