px_tools_4u.c revision fa9e4066f08beec538e775443c5be79dd423fcab
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/cpuvar.h>
#include <sys/ddi_implfuncs.h>
#include <px_csr.h>
#include <px_regs.h>
#include <px_obj.h>
#include <sys/pci_tools.h>
#include <px_tools_var.h>
#include <px_asm_4u.h>
#include <px_lib4u.h>
#include <px_tools_ext.h>
/*
* Delay needed to have a safe environment envelop any error which could
* surface. The larger the number of bridges and switches, the larger the
* number needed here.
*
* Note: this is a workaround until a better solution is found. While this
* number is high, given enough bridges and switches in the device path, this
* workaround can break. Also, other PIL 15 interrupts besides the ones we are
* enveloping could delay processing of the interrupt we are trying to protect.
*/
int pxtool_delay_usec = 500;
/* Number of inos per root complex. */
int pxtool_num_inos = INTERRUPT_MAPPING_ENTRIES;
/* 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;
/*
* Safe C wrapper around assy language routine px_phys_peek_4u
*
* 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
pxtool_safe_phys_peek(px_t *px_p, boolean_t type, size_t size, uint64_t paddr,
uint64_t *value_p)
{
px_pec_t *pec_p = px_p->px_pec_p;
on_trap_data_t otd;
peek_poke_value_t peek_value;
int err = DDI_SUCCESS;
mutex_enter(&pec_p->pec_pokefault_mutex);
pec_p->pec_safeacc_type = DDI_FM_ERR_PEEK;
/*
* 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 px_phys_peek_4u 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 = px_phys_peek_4u(size, paddr, &peek_value.u64, type);
} else
err = DDI_FAILURE;
/*
* Workaround: delay taking down safe access env.
* For more info, see comments where pxtool_delay_usec is declared.
*/
if (pxtool_delay_usec > 0)
drv_usecwait(pxtool_delay_usec);
no_trap();
pec_p->pec_safeacc_type = DDI_FM_ERR_UNEXPECTED;
mutex_exit(&pec_p->pec_pokefault_mutex);
if (err != DDI_FAILURE) {
switch (size) {
case 8:
*value_p = 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 px_phys_poke_4u
*
* 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
pxtool_safe_phys_poke(px_t *px_p, boolean_t type, size_t size, uint64_t paddr,
uint64_t value)
{
on_trap_data_t otd;
px_pec_t *pec_p = px_p->px_pec_p;
peek_poke_value_t poke_value;
int err = DDI_SUCCESS;
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(&pec_p->pec_pokefault_mutex);
pec_p->pec_ontrap_data = &otd;
pec_p->pec_safeacc_type = DDI_FM_ERR_POKE;
/*
* on_trap works like setjmp.
* Set it up to not panic on data access error,
* but to call poke_fault instead.
* Call px_phys_poke_4u 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 = px_phys_poke_4u(size, paddr, &poke_value.u64, type);
} else
err = DDI_FAILURE;
/*
* Workaround: delay taking down safe access env.
* For more info, see comments where pxtool_delay_usec is declared.
*/
if (pxtool_delay_usec > 0)
drv_usecwait(pxtool_delay_usec);
px_lib_clr_errs(px_p);
if (otd.ot_trap & OT_DATA_ACCESS)
err = DDI_FAILURE;
/* Take down protected environment. */
no_trap();
pec_p->pec_ontrap_data = NULL;
pec_p->pec_safeacc_type = DDI_FM_ERR_UNEXPECTED;
mutex_exit(&pec_p->pec_pokefault_mutex);
return (err);
}
/*
* Wrapper around pxtool_safe_phys_peek/poke.
*
* Validates arguments and calls pxtool_safe_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.
*
* This function assumes that offset, bdf, acc_attr, max_addr are current in
* prg_p. It also assumes that prg_p->phys_addr is the final phys addr,
* including offset.
* This function modifies prg_p status and data.
*/
/*ARGSUSED*/
static int
pxtool_access(px_t *px_p, pcitool_reg_t *prg_p, uint64_t max_addr,
uint64_t *data_p, boolean_t is_write)
{
dev_info_t *dip = px_p->px_dip;
uint64_t phys_addr = prg_p->phys_addr;
boolean_t endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg_p->acc_attr);
size_t size = PCITOOL_ACC_ATTR_SIZE(prg_p->acc_attr);
int rval = SUCCESS;
/* Upper bounds checking. */
if (phys_addr > max_addr) {
DBG(DBG_TOOLS, dip,
"Phys addr 0x%" PRIx64 " out of range "
"(max 0x%" PRIx64 ").\n", phys_addr, max_addr);
prg_p->status = PCITOOL_INVALID_ADDRESS;
rval = EINVAL;
/* Alignment checking. Assumes base address is 8-byte aligned. */
} else if (!IS_P2ALIGNED(phys_addr, size)) {
DBG(DBG_TOOLS, dip, "not aligned.\n");
prg_p->status = PCITOOL_NOT_ALIGNED;
rval = EINVAL;
} else if (is_write) { /* Made it through checks. Do the access. */
DBG(DBG_PHYS_ACC, dip,
"%d byte %s pxtool_safe_phys_poke at addr 0x%" PRIx64 "\n",
size, (endian ? "BE" : "LE"), phys_addr);
if (pxtool_safe_phys_poke(px_p, endian, size, phys_addr,
*data_p) != DDI_SUCCESS) {
DBG(DBG_PHYS_ACC, dip,
"%d byte %s pxtool_safe_phys_poke at addr "
"0x%" PRIx64 " failed\n",
size, (endian ? "BE" : "LE"), phys_addr);
prg_p->status = PCITOOL_INVALID_ADDRESS;
rval = EFAULT;
}
} else { /* Read */
DBG(DBG_PHYS_ACC, dip,
"%d byte %s pxtool_safe_phys_peek at addr 0x%" PRIx64 "\n",
size, (endian ? "BE" : "LE"), phys_addr);
if (pxtool_safe_phys_peek(px_p, endian, size, phys_addr,
data_p) != DDI_SUCCESS) {
DBG(DBG_PHYS_ACC, dip,
"%d byte %s pxtool_safe_phys_peek at addr "
"0x%" PRIx64 " failed\n",
size, (endian ? "BE" : "LE"), phys_addr);
prg_p->status = PCITOOL_INVALID_ADDRESS;
rval = EFAULT;
}
}
return (rval);
}
int
pxtool_pcicfg_access(px_t *px_p, pcitool_reg_t *prg_p,
uint64_t max_addr, uint64_t *data_p, boolean_t is_write)
{
return (pxtool_access(px_p, prg_p, max_addr, data_p, is_write));
}
int
pxtool_pciiomem_access(px_t *px_p, pcitool_reg_t *prg_p, uint64_t max_addr,
uint64_t *data_p, boolean_t is_write)
{
return (pxtool_access(px_p, prg_p, max_addr, data_p, is_write));
}
int
pxtool_dev_reg_ops_platchk(dev_info_t *dip, pcitool_reg_t *prg_p)
{
int devi_nodeid = ddi_get_nodeid(dip);
/*
* Guard against checking a root nexus which is empty.
* On some systems this will result in a Fatal Reset.
*/
if ((int)prom_childnode((pnode_t)devi_nodeid) == OBP_NONODE) {
DBG(DBG_TOOLS, dip,
"pxtool_dev_reg_ops set/get reg: nexus has no devs!\n");
prg_p->status = PCITOOL_IO_ERROR;
return (ENXIO);
}
return (SUCCESS);
}
/*
* Perform register accesses on the nexus device itself.
*/
int
pxtool_bus_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
{
pcitool_reg_t prg;
uint64_t base_addr;
uint64_t max_addr;
uint32_t reglen;
px_t *px_p = DIP_TO_STATE(dip);
px_nexus_regspec_t *px_rp = NULL;
uint32_t numbanks = 0;
boolean_t write_flag = B_FALSE;
uint32_t rval = 0;
if (cmd == PCITOOL_NEXUS_SET_REG)
write_flag = B_TRUE;
DBG(DBG_TOOLS, dip, "pxtool_bus_reg_ops set/get reg\n");
/* Read data from userland. */
if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
DDI_SUCCESS) {
DBG(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 **)&px_rp, &reglen) == DDI_SUCCESS) {
if (((reglen * sizeof (int)) %
sizeof (px_nexus_regspec_t)) != 0) {
DBG(DBG_TOOLS, dip, "reg prop not well-formed");
prg.status = PCITOOL_REGPROP_NOTWELLFORMED;
rval = EIO;
goto done;
}
}
numbanks = (reglen * sizeof (int)) / sizeof (px_nexus_regspec_t);
/* Bounds check the bank number. */
if (prg.barnum >= numbanks) {
prg.status = PCITOOL_OUT_OF_RANGE;
rval = EINVAL;
goto done;
}
base_addr = px_rp[prg.barnum].phys_addr;
max_addr = base_addr + px_rp[prg.barnum].size;
prg.phys_addr = base_addr + prg.offset;
DBG(DBG_TOOLS, dip, "pxtool_bus_reg_ops: nexus: base:0x%" PRIx64 ", "
"offset:0x%" PRIx64 ", addr:0x%" PRIx64 ", max_addr:"
"0x%" PRIx64 "\n", base_addr, prg.offset, prg.phys_addr, max_addr);
/* Access device. prg.status is modified. */
rval = pxtool_access(px_p, &prg, max_addr, &prg.data, write_flag);
done:
if (px_rp != NULL)
ddi_prop_free(px_rp);
if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t),
mode) != DDI_SUCCESS) {
DBG(DBG_TOOLS, dip, "Copyout failed.\n");
return (EFAULT);
}
return (rval);
}