px_pci.c revision 00d0963faf2e861a4aef6b1bf28f99a5b2b20755
/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Sun4 PCI Express to PCI bus bridge nexus driver
*/
#include <sys/sysmacros.h>
#include <sys/conf.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/modctl.h>
#include <sys/autoconf.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_subrdefs.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/disp.h>
#include <sys/condvar.h>
#include <sys/callb.h>
#include <sys/pcie.h>
#include <sys/pcie_impl.h>
#include <sys/ddi.h>
#include <sys/sunndi.h>
#include <sys/hotplug/pci/pcihp.h>
#include <sys/hotplug/pci/pciehpc.h>
#include <sys/hotplug/pci/pcishpc.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/file.h>
#include "pcie_pwr.h"
#include "px_pci.h"
#include "px_debug.h"
/* Tunables. Beware: Some are for debug purpose only. */
/*
* PXB MSI tunable:
*
* By default MSI is enabled on all supported platforms.
*/
boolean_t pxb_enable_msi = B_TRUE; /* MSI enabled by default, otherwise INTX */
static int pxb_bus_map(dev_info_t *, dev_info_t *, ddi_map_req_t *,
off_t, off_t, caddr_t *);
static int pxb_ctlops(dev_info_t *, dev_info_t *, ddi_ctl_enum_t,
void *, void *);
static int pxb_intr_ops(dev_info_t *dip, dev_info_t *rdip,
ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result);
/*
* FMA functions
*/
static int pxb_fm_init(pxb_devstate_t *pxb_p);
static void pxb_fm_fini(pxb_devstate_t *pxb_p);
static int pxb_fm_init_child(dev_info_t *dip, dev_info_t *cdip, int cap,
ddi_iblock_cookie_t *ibc_p);
static int pxb_fm_err_callback(dev_info_t *dip, ddi_fm_error_t *derr,
const void *impl_data);
static int ppb_pcie_device_type(pxb_devstate_t *pxb_p);
#ifdef PRINT_PLX_SEEPROM_CRC
static void pxb_print_plx_seeprom_crc_data(pxb_devstate_t *pxb_p);
#endif
struct bus_ops pxb_bus_ops = {
BUSO_REV,
pxb_bus_map,
0,
0,
0,
i_ddi_map_fault,
ddi_dma_map,
ddi_dma_allochdl,
ddi_dma_freehdl,
ddi_dma_bindhdl,
ddi_dma_unbindhdl,
ddi_dma_flush,
ddi_dma_win,
ddi_dma_mctl,
pxb_ctlops,
ddi_bus_prop_op,
ndi_busop_get_eventcookie, /* (*bus_get_eventcookie)(); */
ndi_busop_add_eventcall, /* (*bus_add_eventcall)(); */
ndi_busop_remove_eventcall, /* (*bus_remove_eventcall)(); */
ndi_post_event, /* (*bus_post_event)(); */
NULL, /* (*bus_intr_ctl)(); */
NULL, /* (*bus_config)(); */
NULL, /* (*bus_unconfig)(); */
pxb_fm_init_child, /* (*bus_fm_init)(); */
NULL, /* (*bus_fm_fini)(); */
i_ndi_busop_access_enter, /* (*bus_fm_access_enter)(); */
i_ndi_busop_access_exit, /* (*bus_fm_access_fini)(); */
pcie_bus_power, /* (*bus_power)(); */
pxb_intr_ops /* (*bus_intr_op)(); */
};
static int pxb_open(dev_t *devp, int flags, int otyp, cred_t *credp);
static int pxb_close(dev_t dev, int flags, int otyp, cred_t *credp);
static int pxb_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp);
static int pxb_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int flags, char *name, caddr_t valuep, int *lengthp);
static struct cb_ops pxb_cb_ops = {
pxb_open, /* open */
pxb_close, /* close */
nulldev, /* strategy */
nulldev, /* print */
nulldev, /* dump */
nulldev, /* read */
nulldev, /* write */
pxb_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
pxb_prop_op, /* cb_prop_op */
NULL, /* streamtab */
D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
static int pxb_probe(dev_info_t *);
static int pxb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd);
static int pxb_detach(dev_info_t *devi, ddi_detach_cmd_t cmd);
static int pxb_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg, void **result);
static int pxb_pwr_setup(dev_info_t *dip);
static int pxb_pwr_init_and_raise(dev_info_t *dip, pcie_pwr_t *pwr_p);
static void pxb_pwr_teardown(dev_info_t *dip);
static int pxb_get_cap(ddi_acc_handle_t config_handle, uint8_t cap_id);
static uint16_t pxb_find_ext_cap_reg(ddi_acc_handle_t config_handle,
uint16_t cap_id);
static int pxb_bad_func(pxb_devstate_t *pxb, int func);
static int pxb_check_bad_devs(pxb_devstate_t *pxb, int vend);
/* Hotplug related functions */
static int pxb_pciehpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle);
static int pxb_pcishpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle);
static void pxb_init_hotplug(pxb_devstate_t *pxb);
struct dev_ops pxb_ops = {
DEVO_REV, /* devo_rev */
0, /* refcnt */
pxb_info, /* info */
nulldev, /* identify */
pxb_probe, /* probe */
pxb_attach, /* attach */
pxb_detach, /* detach */
nulldev, /* reset */
&pxb_cb_ops, /* driver operations */
&pxb_bus_ops, /* bus operations */
pcie_power /* power entry */
};
/*
* Module linkage information for the kernel.
*/
static struct modldrv modldrv = {
&mod_driverops, /* Type of module */
"PCIe/PCI nexus driver 1.29",
&pxb_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modldrv,
NULL
};
/*
* soft state pointer and structure template:
*/
void *pxb_state;
/*
* SW workaround for PLX HW bug Flag
*/
int pxb_tlp_count = 64;
/*
* forward function declarations:
*/
static int pxb_intr_init(pxb_devstate_t *pxb, int intr_type);
static void pxb_intr_fini(pxb_devstate_t *pxb);
static uint_t pxb_intr(caddr_t arg1, caddr_t arg2);
static int pxb_intr_attach(pxb_devstate_t *pxb);
static int pxb_get_port_type(dev_info_t *dip, ddi_acc_handle_t config_handle);
static void pxb_removechild(dev_info_t *);
static int pxb_initchild(dev_info_t *child);
static dev_info_t *get_my_childs_dip(dev_info_t *dip, dev_info_t *rdip);
static void pxb_create_ranges_prop(dev_info_t *, ddi_acc_handle_t);
int
_init(void)
{
int e;
if ((e = ddi_soft_state_init(&pxb_state, sizeof (pxb_devstate_t),
1)) == 0 && (e = mod_install(&modlinkage)) != 0)
ddi_soft_state_fini(&pxb_state);
return (e);
}
int
_fini(void)
{
int e;
if ((e = mod_remove(&modlinkage)) == 0)
ddi_soft_state_fini(&pxb_state);
return (e);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*ARGSUSED*/
static int
pxb_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
pxb_devstate_t *pxb_p; /* per pxb state pointer */
minor_t minor = getminor((dev_t)arg);
int instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor);
pxb_p = (pxb_devstate_t *)ddi_get_soft_state(pxb_state,
instance);
switch (infocmd) {
default:
return (DDI_FAILURE);
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(intptr_t)instance;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2DEVINFO:
if (pxb_p == NULL)
return (DDI_FAILURE);
*result = (void *)pxb_p->pxb_dip;
return (DDI_SUCCESS);
}
}
/*ARGSUSED*/
static int
pxb_probe(register dev_info_t *devi)
{
return (DDI_PROBE_SUCCESS);
}
/*ARGSUSED*/
static int
pxb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
int instance;
pxb_devstate_t *pxb;
ddi_acc_handle_t config_handle;
char device_type[8];
instance = ddi_get_instance(devi);
switch (cmd) {
case DDI_RESUME:
DBG(DBG_ATTACH, devi, "DDI_RESUME\n");
/*
* Get the soft state structure for the bridge.
*/
pxb = (pxb_devstate_t *)
ddi_get_soft_state(pxb_state, instance);
(void) pcie_pwr_resume(devi);
return (DDI_SUCCESS);
case DDI_ATTACH:
DBG(DBG_ATTACH, devi, "DDI_ATTACH\n");
/* Follow through to below the switch statement */
break;
default:
return (DDI_FAILURE);
}
/*
* Allocate and get soft state structure.
*/
if (ddi_soft_state_zalloc(pxb_state, instance) != DDI_SUCCESS) {
DBG(DBG_ATTACH, devi, "Unable to allocate soft state.\n");
return (DDI_FAILURE);
}
pxb = (pxb_devstate_t *)ddi_get_soft_state(pxb_state, instance);
pxb->pxb_dip = devi;
pxb->pxb_soft_state = PXB_SOFT_STATE_CLOSED;
/* Create Mutex */
mutex_init(&pxb->pxb_mutex, NULL, MUTEX_DRIVER, NULL);
pxb->pxb_init_flags = PXB_INIT_MUTEX;
/* Setup and save the config space pointer */
if (pci_config_setup(devi, &config_handle) != DDI_SUCCESS) {
DBG(DBG_ATTACH, devi, "Failed in pci_config_setup call\n");
goto fail;
}
pxb->pxb_config_handle = config_handle;
pxb->pxb_init_flags |= PXB_INIT_CONFIG_HANDLE;
/* Save the vnedor id and device id */
pxb->pxb_vendor_id = pci_config_get16(config_handle, PCI_CONF_VENID);
pxb->pxb_device_id = pci_config_get16(config_handle, PCI_CONF_DEVID);
pxb->pxb_rev_id = pci_config_get8(config_handle, PCI_CONF_REVID);
/*
* This is a software workaround to fix the Broadcom PCIe-PCI bridge
* prefetch bug. Create a boolean property and its existence means
* the px nexus driver has to allocate an extra page & make it valid
* one, for any DVMA request that comes from any of the Broadcom child
* device.
*/
if (PXB_IS_BCM5714(pxb)) {
if (ndi_prop_create_boolean(DDI_DEV_T_NONE, pxb->pxb_dip,
"cross-page-prefetch") != DDI_PROP_SUCCESS) {
DBG(DBG_ATTACH, pxb->pxb_dip,
"ndi_prop_create_boolean() failed\n");
goto fail;
}
}
/*
* Power management setup. This also makes sure that switch/bridge
* is at D0 during attach.
*/
if (pwr_common_setup(devi) != DDI_SUCCESS) {
DBG(DBG_PWR, devi, "pwr_common_setup failed\n");
goto fail;
} else if (pxb_pwr_setup(devi) != DDI_SUCCESS) {
DBG(DBG_PWR, devi, "pxb_pwr_setup failed \n");
goto fail;
}
pxb->pxb_port_type = pxb_get_port_type(devi, config_handle);
if ((pxb->pxb_port_type != PX_CAP_REG_DEV_TYPE_UP) &&
(pxb->pxb_port_type != PX_CAP_REG_DEV_TYPE_DOWN) &&
(pxb->pxb_port_type != PX_CAP_REG_DEV_TYPE_PCIE2PCI) &&
(pxb->pxb_port_type != PX_CAP_REG_DEV_TYPE_PCI2PCIE)) {
DBG(DBG_ATTACH, devi, "This is not a switch or bridge\n");
goto fail;
}
/*
* Make sure the "device_type" property exists.
*/
if (ppb_pcie_device_type(pxb) == DDI_SUCCESS)
(void) strcpy(device_type, "pciex");
else
(void) strcpy(device_type, "pci");
(void) ddi_prop_update_string(DDI_DEV_T_NONE, devi,
"device_type", device_type);
/*
* Check whether the "ranges" property is present.
* Otherwise create the ranges property by reading
* the configuration registers
*/
if (ddi_prop_exists(DDI_DEV_T_ANY, devi, DDI_PROP_DONTPASS,
"ranges") == 0) {
pxb_create_ranges_prop(devi, config_handle);
}
/*
* Initialize hotplug support on this bus. At minimum
* (for non hotplug bus) this would create ":devctl" minor
* node to support DEVCTL_DEVICE_* and DEVCTL_BUS_* ioctls
* to this bus. This all takes place if this nexus has hot-plug
* slots and successfully initializes Hot Plug Framework.
*/
pxb->pxb_hotplug_capable = B_FALSE;
if ((pxb->pxb_port_type == PX_CAP_REG_DEV_TYPE_DOWN) ||
(pxb->pxb_port_type == PX_CAP_REG_DEV_TYPE_PCIE2PCI) ||
(pxb->pxb_port_type == PX_CAP_REG_DEV_TYPE_PCI2PCIE)) {
pxb_init_hotplug(pxb);
}
/* attach interrupt only if hotplug functionality is required */
if (pxb->pxb_hotplug_capable != B_FALSE) {
if (pxb_intr_attach(pxb) != DDI_SUCCESS)
goto fail;
}
#ifdef PRINT_PLX_SEEPROM_CRC
/* check seeprom CRC to ensure the platform config is right */
(void) pxb_print_plx_seeprom_crc_data(pxb);
#endif
if (pxb->pxb_hotplug_capable == B_FALSE) {
/*
* create minor node for devctl interfaces
*/
if (ddi_create_minor_node(devi, "devctl", S_IFCHR,
PCIHP_AP_MINOR_NUM(instance, PCIHP_DEVCTL_MINOR),
DDI_NT_NEXUS, 0) != DDI_SUCCESS)
goto fail;
}
DBG(DBG_ATTACH, devi,
"pxb_attach(): this nexus %s hotplug slots\n",
pxb->pxb_hotplug_capable == B_TRUE ? "has":"has no");
if ((pxb_fm_init(pxb)) != DDI_SUCCESS) {
DBG(DBG_ATTACH, devi, "Failed in px_pci_fm_attach\n");
goto fail;
}
pxb->pxb_init_flags |= PXB_INIT_FM;
ddi_report_dev(devi);
return (DDI_SUCCESS);
fail:
(void) pxb_detach(devi, DDI_DETACH);
return (DDI_FAILURE);
}
/*ARGSUSED*/
static int
pxb_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
pxb_devstate_t *pxb;
int error = DDI_SUCCESS;
switch (cmd) {
case DDI_DETACH:
/*
* And finally free the per-pci soft state after
* uninitializing hotplug support for this bus in
* opposite order of attach.
*/
pxb = (pxb_devstate_t *)
ddi_get_soft_state(pxb_state, ddi_get_instance(devi));
if (pxb->pxb_hotplug_capable == B_TRUE) {
if (pcihp_uninit(devi) == DDI_FAILURE)
error = DDI_FAILURE;
if (pxb->pxb_hpc_type == HPC_PCIE)
(void) pciehpc_uninit(devi);
else if (pxb->pxb_hpc_type == HPC_SHPC)
(void) pcishpc_uninit(devi);
}
else
ddi_remove_minor_node(devi, "devctl");
(void) ddi_prop_remove(DDI_DEV_T_NONE, devi, "device_type");
if (pxb->pxb_hotplug_capable != B_FALSE)
pxb_intr_fini(pxb);
if (pxb->pxb_init_flags & PXB_INIT_FM)
pxb_fm_fini(pxb);
(void) ndi_prop_remove(DDI_DEV_T_NONE, pxb->pxb_dip,
"cross-page-prefetch");
if (pxb->pxb_init_flags & PXB_INIT_CONFIG_HANDLE)
pci_config_teardown(&pxb->pxb_config_handle);
pxb_pwr_teardown(devi);
pwr_common_teardown(devi);
if (pxb->pxb_init_flags & PXB_INIT_MUTEX)
mutex_destroy(&pxb->pxb_mutex);
ddi_soft_state_free(pxb_state, ddi_get_instance(devi));
return (error);
case DDI_SUSPEND:
pxb = (pxb_devstate_t *)
ddi_get_soft_state(pxb_state, ddi_get_instance(devi));
error = pcie_pwr_suspend(devi);
return (error);
}
return (DDI_FAILURE);
}
/*ARGSUSED*/
static int
pxb_bus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t offset, off_t len, caddr_t *vaddrp)
{
register dev_info_t *pdip;
pdip = (dev_info_t *)DEVI(dip)->devi_parent;
return ((DEVI(pdip)->devi_ops->devo_bus_ops->bus_map)
(pdip, rdip, mp, offset, len, vaddrp));
}
/*ARGSUSED*/
static int
pxb_ctlops(dev_info_t *dip, dev_info_t *rdip,
ddi_ctl_enum_t ctlop, void *arg, void *result)
{
pci_regspec_t *drv_regp;
int reglen;
int rn;
int totreg;
struct detachspec *ds;
struct attachspec *as;
switch (ctlop) {
case DDI_CTLOPS_REPORTDEV:
if (rdip == (dev_info_t *)0)
return (DDI_FAILURE);
cmn_err(CE_CONT, "?PCI-device: %s@%s, %s%d\n",
ddi_node_name(rdip), ddi_get_name_addr(rdip),
ddi_driver_name(rdip),
ddi_get_instance(rdip));
return (DDI_SUCCESS);
case DDI_CTLOPS_INITCHILD:
return (pxb_initchild((dev_info_t *)arg));
case DDI_CTLOPS_UNINITCHILD:
pxb_removechild((dev_info_t *)arg);
return (DDI_SUCCESS);
case DDI_CTLOPS_SIDDEV:
return (DDI_SUCCESS);
case DDI_CTLOPS_REGSIZE:
case DDI_CTLOPS_NREGS:
if (rdip == (dev_info_t *)0)
return (DDI_FAILURE);
break;
case DDI_CTLOPS_ATTACH:
as = (struct attachspec *)arg;
switch (as->when) {
case DDI_PRE:
if (as->cmd == DDI_ATTACH) {
DBG(DBG_PWR, dip, "PRE_ATTACH for %s@%d\n",
ddi_driver_name(rdip),
ddi_get_instance(rdip));
return (pcie_pm_hold(dip));
}
if (as->cmd == DDI_RESUME) {
ddi_acc_handle_t config_handle;
DBG(DBG_PWR, dip, "PRE_RESUME for %s@%d\n",
ddi_driver_name(rdip),
ddi_get_instance(rdip));
if (pci_config_setup(rdip, &config_handle) ==
DDI_SUCCESS) {
pcie_clear_errors(rdip, config_handle);
pci_config_teardown(&config_handle);
}
}
return (DDI_SUCCESS);
case DDI_POST:
DBG(DBG_PWR, dip, "POST_ATTACH for %s@%d\n",
ddi_driver_name(rdip), ddi_get_instance(rdip));
if (as->cmd == DDI_ATTACH && as->result != DDI_SUCCESS)
pcie_pm_release(dip);
return (DDI_SUCCESS);
default:
break;
}
break;
case DDI_CTLOPS_DETACH:
ds = (struct detachspec *)arg;
switch (ds->when) {
case DDI_POST:
if (ds->cmd == DDI_DETACH &&
ds->result == DDI_SUCCESS) {
DBG(DBG_PWR, dip, "POST_DETACH for %s@%d\n",
ddi_driver_name(rdip),
ddi_get_instance(rdip));
return (pcie_pm_remove_child(dip, rdip));
}
return (DDI_SUCCESS);
default:
break;
}
break;
default:
return (ddi_ctlops(dip, rdip, ctlop, arg, result));
}
*(int *)result = 0;
if (ddi_getlongprop(DDI_DEV_T_ANY, rdip,
DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "reg",
(caddr_t)&drv_regp, &reglen) != DDI_SUCCESS)
return (DDI_FAILURE);
totreg = reglen / sizeof (pci_regspec_t);
if (ctlop == DDI_CTLOPS_NREGS)
*(int *)result = totreg;
else if (ctlop == DDI_CTLOPS_REGSIZE) {
rn = *(int *)arg;
if (rn >= totreg) {
kmem_free(drv_regp, reglen);
return (DDI_FAILURE);
}
*(off_t *)result = drv_regp[rn].pci_size_low |
((uint64_t)drv_regp[rn].pci_size_hi << 32);
}
kmem_free(drv_regp, reglen);
return (DDI_SUCCESS);
}
static dev_info_t *
get_my_childs_dip(dev_info_t *dip, dev_info_t *rdip)
{
dev_info_t *cdip = rdip;
for (; ddi_get_parent(cdip) != dip; cdip = ddi_get_parent(cdip))
;
return (cdip);
}
static int
pxb_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
ddi_intr_handle_impl_t *hdlp, void *result)
{
dev_info_t *cdip = rdip;
pci_regspec_t *pci_rp;
int reglen, len;
uint32_t d, intr;
if (hdlp->ih_type != DDI_INTR_TYPE_FIXED)
goto done;
/*
* If the interrupt-map property is defined at this
* node, it will have performed the interrupt
* translation as part of the property, so no
* rotation needs to be done.
*/
if (ddi_getproplen(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"interrupt-map", &len) == DDI_PROP_SUCCESS)
goto done;
cdip = get_my_childs_dip(dip, rdip);
/*
* Use the devices reg property to determine its
* PCI bus number and device number.
*/
if (ddi_getlongprop(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
"reg", (caddr_t)&pci_rp, &reglen) != DDI_SUCCESS)
return (DDI_FAILURE);
intr = hdlp->ih_vector;
/* spin the interrupt */
d = PCI_REG_DEV_G(pci_rp[0].pci_phys_hi);
if ((intr >= PCI_INTA) && (intr <= PCI_INTD))
hdlp->ih_vector = ((intr - 1 + (d % 4)) % 4 + 1);
else
cmn_err(CE_WARN, "%s%d: %s: PCI intr=%x out of range",
ddi_driver_name(rdip), ddi_get_instance(rdip),
ddi_driver_name(dip), intr);
kmem_free(pci_rp, reglen);
done:
/* Pass up the request to our parent. */
return (i_ddi_intr_ops(dip, rdip, intr_op, hdlp, result));
}
/*
* name_child
*
* This function is called from init_child to name a node. It is
* also passed as a callback for node merging functions.
*
* return value: DDI_SUCCESS, DDI_FAILURE
*/
static int
pxb_name_child(dev_info_t *child, char *name, int namelen)
{
pci_regspec_t *pci_rp;
uint_t slot, func;
char **unit_addr;
uint_t n;
/*
* Pseudo nodes indicate a prototype node with per-instance
* properties to be merged into the real h/w device node.
* The interpretation of the unit-address is DD[,F]
* where DD is the device id and F is the function.
*/
if (ndi_dev_is_persistent_node(child) == 0) {
if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS, "unit-address", &unit_addr, &n) !=
DDI_PROP_SUCCESS) {
cmn_err(CE_WARN, "cannot name node from %s.conf",
ddi_driver_name(child));
return (DDI_FAILURE);
}
if (n != 1 || *unit_addr == NULL || **unit_addr == 0) {
cmn_err(CE_WARN, "unit-address property in %s.conf"
" not well-formed", ddi_driver_name(child));
ddi_prop_free(unit_addr);
return (DDI_FAILURE);
}
(void) snprintf(name, namelen, "%s", *unit_addr);
ddi_prop_free(unit_addr);
return (DDI_SUCCESS);
}
/*
* Get the address portion of the node name based on
* the function and device number.
*/
if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
"reg", (int **)&pci_rp, &n) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
slot = PCI_REG_DEV_G(pci_rp[0].pci_phys_hi);
func = PCI_REG_FUNC_G(pci_rp[0].pci_phys_hi);
if (func != 0)
(void) snprintf(name, namelen, "%x,%x", slot, func);
else
(void) snprintf(name, namelen, "%x", slot);
ddi_prop_free(pci_rp);
return (DDI_SUCCESS);
}
static int
pxb_initchild(dev_info_t *child)
{
ddi_acc_handle_t config_handle;
char name[MAXNAMELEN];
pxb_devstate_t *pxb;
int result = DDI_FAILURE;
int i;
uint16_t reg = 0;
/*
* Name the child
*/
if (pxb_name_child(child, name, MAXNAMELEN) != DDI_SUCCESS) {
result = DDI_FAILURE;
goto done;
}
ddi_set_name_addr(child, name);
ddi_set_parent_data(child, NULL);
/*
* Pseudo nodes indicate a prototype node with per-instance
* properties to be merged into the real h/w device node.
* The interpretation of the unit-address is DD[,F]
* where DD is the device id and F is the function.
*/
if (ndi_dev_is_persistent_node(child) == 0) {
extern int pci_allow_pseudo_children;
/*
* Try to merge the properties from this prototype
* node into real h/w nodes.
*/
if (ndi_merge_node(child, pxb_name_child) == DDI_SUCCESS) {
/*
* Merged ok - return failure to remove the node.
*/
pxb_removechild(child);
result = DDI_FAILURE;
goto done;
}
/* workaround for ddivs to run under PCI */
if (pci_allow_pseudo_children) {
result = DDI_SUCCESS;
goto done;
}
/*
* The child was not merged into a h/w node,
* but there's not much we can do with it other
* than return failure to cause the node to be removed.
*/
cmn_err(CE_WARN, "!%s@%s: %s.conf properties not merged",
ddi_driver_name(child), ddi_get_name_addr(child),
ddi_driver_name(child));
pxb_removechild(child);
result = DDI_NOT_WELL_FORMED;
goto done;
}
ddi_set_parent_data(child, NULL);
pxb = (pxb_devstate_t *)ddi_get_soft_state(pxb_state,
ddi_get_instance(ddi_get_parent(child)));
if (pcie_pm_hold(pxb->pxb_dip) != DDI_SUCCESS) {
DBG(DBG_PWR, pxb->pxb_dip,
"INITCHILD: px_pm_hold failed\n");
result = DDI_FAILURE;
goto done;
}
/* Any return from here must call pcie_pm_release */
/*
* If configuration registers were previously saved by
* child (before it entered D3), then let the child do the
* restore to set up the config regs as it'll first need to
* power the device out of D3.
*/
if (ddi_prop_exists(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
"config-regs-saved-by-child") == 1) {
DBG(DBG_PWR, ddi_get_parent(child),
"INITCHILD: config regs to be restored by child"
" for %s@%s\n", ddi_node_name(child),
ddi_get_name_addr(child));
result = DDI_SUCCESS;
goto cleanup;
}
DBG(DBG_PWR, ddi_get_parent(child),
"INITCHILD: config regs setup for %s@%s\n",
ddi_node_name(child), ddi_get_name_addr(child));
if (pcie_initchild(child) != DDI_SUCCESS) {
result = DDI_FAILURE;
goto cleanup;
}
/*
* Due to a PLX HW bug, a SW workaround to prevent the chip from
* wedging is needed. SW just needs to tranfer 64 TLPs from
* the downstream port to the child device.
* The most benign way of doing this is to read the ID register
* 64 times. This SW workaround should have minimum performance
* impact and shouldn't cause a problem for all other bridges
* and switches.
*
* The code needs to be written in a way to make sure it isn't
* optimized out.
*/
if ((!pxb_tlp_count) ||
((pxb->pxb_vendor_id != PXB_VENDOR_SUN) &&
(pxb->pxb_vendor_id != PXB_VENDOR_PLX)) ||
((pxb->pxb_vendor_id == PXB_VENDOR_SUN) &&
(pxb->pxb_device_id != PXB_DEVICE_PLX_PCIX) &&
(pxb->pxb_device_id != PXB_DEVICE_PLX_PCIE)) ||
((pxb->pxb_vendor_id == PXB_VENDOR_PLX) &&
(pxb->pxb_device_id != PXB_DEVICE_PLX_8532) &&
(pxb->pxb_device_id != PXB_DEVICE_PLX_8516))) {
/* Workaround not needed return success */
result = DDI_SUCCESS;
goto cleanup;
}
if (pci_config_setup(child, &config_handle) != DDI_SUCCESS) {
result = DDI_FAILURE;
goto cleanup;
}
for (i = 0; i < pxb_tlp_count; i += 1)
reg |= pci_config_get16(config_handle, PCI_CONF_VENID);
pci_config_teardown(&config_handle);
result = DDI_SUCCESS;
cleanup:
pcie_pm_release(pxb->pxb_dip);
done:
return (result);
}
static int
pxb_intr_attach(pxb_devstate_t *pxb)
{
int intr_types;
dev_info_t *devi;
uint8_t bad_msi_dev = 0;
devi = pxb->pxb_dip;
/*
* Initialize interrupt handlers.
* If both MSI and FIXED are supported, try to attach MSI first.
* If MSI fails for any reason, then try FIXED, but only allow one
* type to be attached.
*/
if (ddi_intr_get_supported_types(devi, &intr_types) != DDI_SUCCESS) {
DBG(DBG_ATTACH, devi, "ddi_intr_get_supported_types failed\n");
return (DDI_FAILURE);
}
if (pxb_bad_func(pxb, PXB_MSI))
bad_msi_dev = 1;
if ((intr_types & DDI_INTR_TYPE_MSI) && pxb_enable_msi &&
!bad_msi_dev) {
if (pxb_intr_init(pxb, DDI_INTR_TYPE_MSI) == DDI_SUCCESS)
intr_types = DDI_INTR_TYPE_MSI;
else
DBG(DBG_ATTACH, devi, "Unable to attach MSI handler\n");
}
if (intr_types & DDI_INTR_TYPE_FIXED) {
if (pxb_intr_init(pxb, DDI_INTR_TYPE_FIXED) != DDI_SUCCESS) {
DBG(DBG_ATTACH, devi,
"Unable to attach INTx handler\n");
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/*
* This function initializes internally generated interrupts only.
* It does not affect any interrupts generated by downstream devices
* or the forwarding of them.
*
* Enable Device Specific Interrupts or Hotplug features here.
* Enabling features may change how many interrupts are requested
* by the device. If features are not enabled first, the
* device might not ask for any interrupts.
*/
static int
pxb_intr_init(pxb_devstate_t *pxb, int intr_type)
{
dev_info_t *dip = pxb->pxb_dip;
int request, count, x;
int ret;
int intr_cap = 0;
DBG(DBG_ATTACH, dip,
"Attaching %s handler\n",
(intr_type == DDI_INTR_TYPE_MSI) ? "MSI" : "INTx");
/*
* Get number of requested interrupts. If none requested or DDI_FAILURE
* just return DDI_SUCCESS.
*
* Several Bridges/Switches will not have this property set, resulting
* in a FAILURE, if the device is not configured in a way that
* interrupts are needed. (eg. hotplugging)
*/
ret = ddi_intr_get_nintrs(dip, intr_type, &request);
if (ret != DDI_SUCCESS || request == 0) {
DBG(DBG_ATTACH, dip,
"ddi_intr_get_nintrs() ret: %d req %d\n", ret, request);
return (DDI_SUCCESS);
}
/* Find out how many MSI's are available. */
if (intr_type == DDI_INTR_TYPE_MSI) {
ret = ddi_intr_get_navail(dip, intr_type, &count);
if ((ret != DDI_SUCCESS) || (count == 0)) {
DBG(DBG_ATTACH, dip,
"ddi_intr_get_navail() ret: %d available: %d\n",
ret, count);
goto fail;
}
if (request < count) {
DBG(DBG_ATTACH, dip,
"Requested Intr: %d Available: %d\n",
request, count);
request = count;
}
}
/* Allocate an array of interrupt handlers */
pxb->pxb_htable_size = sizeof (ddi_intr_handle_t) * request;
pxb->pxb_htable = kmem_zalloc(pxb->pxb_htable_size, KM_SLEEP);
pxb->pxb_init_flags |= PXB_INIT_HTABLE;
ret = ddi_intr_alloc(dip, pxb->pxb_htable, intr_type,
0, request, &count, DDI_INTR_ALLOC_NORMAL);
if ((ret != DDI_SUCCESS) || (count == 0)) {
DBG(DBG_ATTACH, dip,
"ddi_intr_alloc() ret: %d ask: %d actual: %d\n",
ret, request, count);
goto fail;
}
/* Save the actually number of interrupts allocated */
pxb->pxb_intr_count = count;
if (count < request) {
DBG(DBG_ATTACH, dip,
"Requested Intr: %d Received: %d\n",
request, count);
}
pxb->pxb_init_flags |= PXB_INIT_ALLOC;
/* Get interrupt priority */
ret = ddi_intr_get_pri(pxb->pxb_htable[0], &pxb->pxb_intr_priority);
if (ret != DDI_SUCCESS) {
DBG(DBG_ATTACH, dip, "ddi_intr_get_pri() ret: %d\n", ret);
goto fail;
}
if (pxb->pxb_intr_priority >= LOCK_LEVEL) {
pxb->pxb_intr_priority = LOCK_LEVEL - 1;
ret = ddi_intr_set_pri(pxb->pxb_htable[0],
pxb->pxb_intr_priority);
if (ret != DDI_SUCCESS) {
DBG(DBG_ATTACH, dip, "ddi_intr_set_pri() ret: %d\n",
ret);
goto fail;
}
}
for (count = 0; count < pxb->pxb_intr_count; count++) {
ret = ddi_intr_add_handler(pxb->pxb_htable[count],
pxb_intr, (caddr_t)pxb, NULL);
if (ret != DDI_SUCCESS) {
DBG(DBG_ATTACH, dip,
"ddi_intr_add_handler() ret: %d\n",
ret);
break;
}
}
/* If unsucessful remove the added handlers */
if (ret != DDI_SUCCESS) {
for (x = 0; x < count; x++) {
(void) ddi_intr_remove_handler(pxb->pxb_htable[x]);
}
goto fail;
}
pxb->pxb_init_flags |= PXB_INIT_HANDLER;
(void) ddi_intr_get_cap(pxb->pxb_htable[0], &intr_cap);
if (intr_cap & DDI_INTR_FLAG_BLOCK) {
(void) ddi_intr_block_enable(pxb->pxb_htable,
pxb->pxb_intr_count);
pxb->pxb_init_flags |= PXB_INIT_BLOCK;
} else {
for (count = 0; count < pxb->pxb_intr_count; count++) {
(void) ddi_intr_enable(pxb->pxb_htable[count]);
}
}
pxb->pxb_init_flags |= PXB_INIT_ENABLE;
/* Save the interrupt type */
pxb->pxb_intr_type = intr_type;
return (DDI_SUCCESS);
fail:
pxb_intr_fini(pxb);
return (DDI_FAILURE);
}
static void
pxb_intr_fini(pxb_devstate_t *pxb)
{
int x;
int count = pxb->pxb_intr_count;
int flags = pxb->pxb_init_flags;
if ((flags & PXB_INIT_ENABLE) && (flags & PXB_INIT_BLOCK)) {
(void) ddi_intr_block_disable(pxb->pxb_htable, count);
flags &= ~(PXB_INIT_ENABLE | PXB_INIT_BLOCK);
}
for (x = 0; x < count; x++) {
if (flags & PXB_INIT_ENABLE)
(void) ddi_intr_disable(pxb->pxb_htable[x]);
if (flags & PXB_INIT_HANDLER)
(void) ddi_intr_remove_handler(pxb->pxb_htable[x]);
if (flags & PXB_INIT_ALLOC)
(void) ddi_intr_free(pxb->pxb_htable[x]);
}
flags &= ~(PXB_INIT_ENABLE | PXB_INIT_HANDLER | PXB_INIT_ALLOC);
if (flags & PXB_INIT_HTABLE)
kmem_free(pxb->pxb_htable, pxb->pxb_htable_size);
flags &= ~PXB_INIT_HTABLE;
pxb->pxb_init_flags &= flags;
}
/*
* This only handles internal errors, not bus errors.
* Currently the only known interrupt would be from hotplugging.
*/
/*ARGSUSED*/
static uint_t
pxb_intr(caddr_t arg1, caddr_t arg2)
{
pxb_devstate_t *pxb = (pxb_devstate_t *)arg1;
dev_info_t *dip = pxb->pxb_dip;
int rval = DDI_INTR_UNCLAIMED;
if (pxb->pxb_hotplug_capable == B_TRUE) {
if (pxb->pxb_hpc_type == HPC_PCIE)
rval = pciehpc_intr(pxb->pxb_dip);
else
if (pxb->pxb_hpc_type == HPC_SHPC)
rval = pcishpc_intr(pxb->pxb_dip);
}
if ((rval == DDI_INTR_UNCLAIMED) && (pxb->pxb_intr_type ==
DDI_INTR_TYPE_MSI))
cmn_err(CE_WARN, "%s%d: Cannot handle interrupt",
ddi_driver_name(dip), ddi_get_instance(dip));
return (rval);
}
static int
pxb_get_port_type(dev_info_t *dip, ddi_acc_handle_t config_handle)
{
ushort_t caps_ptr, cap;
int port_type = PX_CAP_REG_DEV_TYPE_PCIE_DEV;
/*
* Check if capabilities list is supported. If not then it is a PCI
* device. If so, check to see if it contains PCI Express Capability
* Register. Eventually move this code to PCI-EX Framework.
*/
if (pci_config_get16(config_handle, PCI_CONF_STAT)
& PCI_STAT_CAP)
caps_ptr = P2ALIGN(pci_config_get8(config_handle,
PCI_CONF_CAP_PTR), 4);
else
caps_ptr = PCI_CAP_NEXT_PTR_NULL;
while (caps_ptr != PCI_CAP_NEXT_PTR_NULL) {
if (caps_ptr < 0x40) {
DBG(DBG_ATTACH, dip,
"Capability pointer 0x%x out of range.\n",
caps_ptr);
break;
}
cap = pci_config_get8(config_handle, caps_ptr);
if (cap == PCI_CAP_ID_PCI_E) {
port_type = pci_config_get16(config_handle,
caps_ptr + PX_CAP_REG) & PX_CAP_REG_DEV_TYPE_MASK;
break;
}
caps_ptr = P2ALIGN(pci_config_get8(config_handle,
(caps_ptr + PCI_CAP_NEXT_PTR)), 4);
}
return (port_type);
}
static void
pxb_removechild(dev_info_t *dip)
{
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);
pcie_uninitchild(dip);
}
/*
* Initialize hotplug framework if we are hotpluggable.
* Sets flag in the soft state if Hot Plug is supported and initialized
* properly.
*/
/*ARGSUSED*/
static void
pxb_init_hotplug(pxb_devstate_t *pxb)
{
int rv;
pxb->pxb_hpc_type = HPC_NONE;
if (((pxb->pxb_port_type == PX_CAP_REG_DEV_TYPE_DOWN) ||
(pxb->pxb_port_type == PX_CAP_REG_DEV_TYPE_PCI2PCIE)) &&
(pxb_pciehpc_probe(pxb->pxb_dip,
pxb->pxb_config_handle) == DDI_SUCCESS)) {
rv = pciehpc_init(pxb->pxb_dip, NULL);
if (rv == DDI_SUCCESS)
pxb->pxb_hpc_type = HPC_PCIE;
} else if ((pxb->pxb_port_type == PX_CAP_REG_DEV_TYPE_PCIE2PCI) &&
(pxb_pcishpc_probe(pxb->pxb_dip,
pxb->pxb_config_handle) == DDI_SUCCESS)) {
rv = pcishpc_init(pxb->pxb_dip);
if (rv == DDI_SUCCESS)
pxb->pxb_hpc_type = HPC_SHPC;
}
if (pxb->pxb_hpc_type != HPC_NONE) {
if (pcihp_init(pxb->pxb_dip) != DDI_SUCCESS) {
if (pxb->pxb_hpc_type == HPC_PCIE)
(void) pciehpc_uninit(pxb->pxb_dip);
else if (pxb->pxb_hpc_type == HPC_SHPC)
(void) pcishpc_uninit(pxb->pxb_dip);
pxb->pxb_hpc_type = HPC_NONE;
cmn_err(CE_WARN,
"%s%d: Failed setting hotplug framework",
ddi_driver_name(pxb->pxb_dip),
ddi_get_instance(pxb->pxb_dip));
}
}
pxb->pxb_hotplug_capable = (pxb->pxb_hpc_type != HPC_NONE);
}
static void
pxb_create_ranges_prop(dev_info_t *dip,
ddi_acc_handle_t config_handle)
{
uint32_t base, limit;
pxb_ranges_t ranges[PXB_RANGE_LEN];
uint8_t io_base_lo, io_limit_lo;
uint16_t io_base_hi, io_limit_hi, mem_base, mem_limit;
int i = 0, rangelen = sizeof (pxb_ranges_t)/sizeof (int);
io_base_lo = pci_config_get8(config_handle, PCI_BCNF_IO_BASE_LOW);
io_limit_lo = pci_config_get8(config_handle, PCI_BCNF_IO_LIMIT_LOW);
io_base_hi = pci_config_get16(config_handle, PCI_BCNF_IO_BASE_HI);
io_limit_hi = pci_config_get16(config_handle, PCI_BCNF_IO_LIMIT_HI);
mem_base = pci_config_get16(config_handle, PCI_BCNF_MEM_BASE);
mem_limit = pci_config_get16(config_handle, PCI_BCNF_MEM_LIMIT);
/*
* Create ranges for IO space
*/
ranges[i].size_low = ranges[i].size_high = 0;
ranges[i].parent_mid = ranges[i].child_mid =
ranges[i].parent_high = 0;
ranges[i].child_high = ranges[i].parent_high |=
(PCI_REG_REL_M | PCI_ADDR_IO);
base = PXB_16bit_IOADDR(io_base_lo);
limit = PXB_16bit_IOADDR(io_limit_lo);
if ((io_base_lo & 0xf) == PXB_32BIT_IO) {
base = PXB_LADDR(base, io_base_hi);
}
if ((io_limit_lo & 0xf) == PXB_32BIT_IO) {
limit = PXB_LADDR(limit, io_limit_hi);
}
if ((io_base_lo & PXB_32BIT_IO) && (io_limit_hi > 0)) {
base = PXB_LADDR(base, io_base_hi);
limit = PXB_LADDR(limit, io_limit_hi);
}
/*
* Create ranges for 32bit memory space
*/
base = PXB_32bit_MEMADDR(mem_base);
limit = PXB_32bit_MEMADDR(mem_limit);
ranges[i].size_low = ranges[i].size_high = 0;
ranges[i].parent_mid = ranges[i].child_mid =
ranges[i].parent_high = 0;
ranges[i].child_high = ranges[i].parent_high |=
(PCI_REG_REL_M | PCI_ADDR_MEM32);
ranges[i].child_low = ranges[i].parent_low = base;
if (limit >= base) {
ranges[i].size_low = limit - base + PXB_MEMGRAIN;
i++;
}
if (i) {
(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, dip, "ranges",
(int *)ranges, i * rangelen);
}
}
/*ARGSUSED*/
static int
pxb_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
pxb_devstate_t *pxb_p;
minor_t minor = getminor(*devp);
int instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor);
/*
* Make sure the open is for the right file type.
*/
if (otyp != OTYP_CHR)
return (EINVAL);
/*
* Get the soft state structure for the device.
*/
pxb_p = (pxb_devstate_t *)ddi_get_soft_state(pxb_state,
instance);
if (pxb_p == NULL)
return (ENXIO);
if (pxb_p->pxb_hotplug_capable == B_TRUE)
return ((pcihp_get_cb_ops())->cb_open(devp, flags,
otyp, credp));
/*
* Handle the open by tracking the device state.
*/
mutex_enter(&pxb_p->pxb_mutex);
if (flags & FEXCL) {
if (pxb_p->pxb_soft_state != PXB_SOFT_STATE_CLOSED) {
mutex_exit(&pxb_p->pxb_mutex);
return (EBUSY);
}
pxb_p->pxb_soft_state = PXB_SOFT_STATE_OPEN_EXCL;
} else {
if (pxb_p->pxb_soft_state == PXB_SOFT_STATE_OPEN_EXCL) {
mutex_exit(&pxb_p->pxb_mutex);
return (EBUSY);
}
pxb_p->pxb_soft_state = PXB_SOFT_STATE_OPEN;
}
mutex_exit(&pxb_p->pxb_mutex);
return (0);
}
/*ARGSUSED*/
static int
pxb_close(dev_t dev, int flags, int otyp, cred_t *credp)
{
pxb_devstate_t *pxb_p;
minor_t minor = getminor(dev);
int instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor);
if (otyp != OTYP_CHR)
return (EINVAL);
pxb_p = (pxb_devstate_t *)ddi_get_soft_state(pxb_state,
instance);
if (pxb_p == NULL)
return (ENXIO);
if (pxb_p->pxb_hotplug_capable == B_TRUE)
return ((pcihp_get_cb_ops())->cb_close(dev, flags,
otyp, credp));
mutex_enter(&pxb_p->pxb_mutex);
pxb_p->pxb_soft_state = PXB_SOFT_STATE_CLOSED;
mutex_exit(&pxb_p->pxb_mutex);
return (0);
}
/*
* pxb_ioctl: devctl hotplug controls
*/
/*ARGSUSED*/
static int
pxb_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rvalp)
{
pxb_devstate_t *pxb_p;
dev_info_t *self;
struct devctl_iocdata *dcp;
uint_t bus_state;
int rv = 0;
minor_t minor = getminor(dev);
int instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor);
pxb_p = (pxb_devstate_t *)ddi_get_soft_state(pxb_state,
instance);
if (pxb_p == NULL)
return (ENXIO);
if (pxb_p->pxb_hotplug_capable == B_TRUE)
return ((pcihp_get_cb_ops())->cb_ioctl(dev, cmd,
arg, mode, credp, rvalp));
self = pxb_p->pxb_dip;
/*
* We can use the generic implementation for these ioctls
*/
switch (cmd) {
case DEVCTL_DEVICE_GETSTATE:
case DEVCTL_DEVICE_ONLINE:
case DEVCTL_DEVICE_OFFLINE:
case DEVCTL_BUS_GETSTATE:
return (ndi_devctl_ioctl(self, cmd, arg, mode, 0));
}
/*
* read devctl ioctl data
*/
if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)
return (EFAULT);
switch (cmd) {
case DEVCTL_DEVICE_RESET:
rv = ENOTSUP;
break;
case DEVCTL_BUS_QUIESCE:
if (ndi_get_bus_state(self, &bus_state) == NDI_SUCCESS)
if (bus_state == BUS_QUIESCED)
break;
(void) ndi_set_bus_state(self, BUS_QUIESCED);
break;
case DEVCTL_BUS_UNQUIESCE:
if (ndi_get_bus_state(self, &bus_state) == NDI_SUCCESS)
if (bus_state == BUS_ACTIVE)
break;
(void) ndi_set_bus_state(self, BUS_ACTIVE);
break;
case DEVCTL_BUS_RESET:
rv = ENOTSUP;
break;
case DEVCTL_BUS_RESETALL:
rv = ENOTSUP;
break;
default:
rv = ENOTTY;
}
ndi_dc_freehdl(dcp);
return (rv);
}
static int pxb_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int flags, char *name, caddr_t valuep, int *lengthp)
{
pxb_devstate_t *pxb_p;
minor_t minor = getminor(dev);
int instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor);
pxb_p = (pxb_devstate_t *)ddi_get_soft_state(pxb_state,
instance);
if (pxb_p == NULL)
return (ENXIO);
if (pxb_p->pxb_hotplug_capable == B_TRUE)
return ((pcihp_get_cb_ops())->cb_prop_op(dev, dip, prop_op,
flags, name, valuep, lengthp));
return (ddi_prop_op(dev, dip, prop_op, flags, name, valuep, lengthp));
}
/*
* Power management related initialization specific to px_pci.
* Called by pxb_attach()
*/
static int
pxb_pwr_setup(dev_info_t *dip)
{
char *comp_array[5];
int i, instance;
ddi_acc_handle_t conf_hdl;
uint8_t cap_ptr, cap_id;
uint16_t pmcap;
pcie_pwr_t *pwr_p;
pxb_devstate_t *pxb;
ASSERT(PCIE_PMINFO(dip));
pwr_p = PCIE_NEXUS_PMINFO(dip);
ASSERT(pwr_p);
instance = ddi_get_instance(dip);
pxb = (pxb_devstate_t *)ddi_get_soft_state(pxb_state, instance);
/*
* Disable PM for PLX 8532 switch. Transitioning one port on
* this switch to low power causes links on other ports on the
* same station to die.
* Due to PLX erratum #34, we can't allow the downstream device
* go to non-D0 state.
*/
if (((pxb->pxb_vendor_id == PXB_VENDOR_SUN) &&
((pxb->pxb_device_id == PXB_DEVICE_PLX_PCIX) ||
(pxb->pxb_device_id == PXB_DEVICE_PLX_PCIE))) ||
((pxb->pxb_vendor_id == PXB_VENDOR_PLX) &&
((pxb->pxb_device_id == PXB_DEVICE_PLX_8516) ||
(pxb->pxb_device_id == PXB_DEVICE_PLX_8532)))) {
DBG(DBG_PWR, dip, "pxb_pwr_setup: PLX8532/PLX8516 found "
"disabling PM\n");
pwr_p->pwr_func_lvl = PM_LEVEL_D0;
pwr_p->pwr_flags = PCIE_NO_CHILD_PM;
return (DDI_SUCCESS);
}
/* Code taken from pci_pci driver */
if (pci_config_setup(dip, &pwr_p->pwr_conf_hdl) != DDI_SUCCESS) {
DBG(DBG_PWR, dip, "pxb_pwr_setup: pci_config_setup failed\n");
return (DDI_FAILURE);
}
conf_hdl = pwr_p->pwr_conf_hdl;
cap_ptr = pci_config_get8(conf_hdl, PCI_BCNF_CAP_PTR);
/*
* Walk the capabilities searching for a PM entry.
*/
while (cap_ptr != PCI_CAP_NEXT_PTR_NULL) {
cap_id = pci_config_get8(conf_hdl,
cap_ptr + PCI_CAP_ID);
if (cap_id == PCI_CAP_ID_PM) {
break;
}
cap_ptr = pci_config_get8(conf_hdl,
cap_ptr + PCI_CAP_NEXT_PTR);
}
if (cap_ptr == 0) {
DBG(DBG_PWR, dip, "switch/bridge does not support PM. PCI"
" PM data structure not found in config header\n");
pci_config_teardown(&conf_hdl);
return (DDI_SUCCESS);
}
/*
* Save offset to pmcsr for future references.
*/
pwr_p->pwr_pmcsr_offset = cap_ptr + PCI_PMCSR;
pmcap = pci_config_get16(conf_hdl, cap_ptr + PCI_PMCAP);
if (pmcap & PCI_PMCAP_D1) {
DBG(DBG_PWR, dip, "D1 state supported\n");
pwr_p->pwr_pmcaps |= PCIE_SUPPORTS_D1;
}
if (pmcap & PCI_PMCAP_D2) {
DBG(DBG_PWR, dip, "D2 state supported\n");
pwr_p->pwr_pmcaps |= PCIE_SUPPORTS_D2;
}
i = 0;
comp_array[i++] = "NAME=PCIe switch/bridge PM";
comp_array[i++] = "0=Power Off (D3)";
if (pwr_p->pwr_pmcaps & PCIE_SUPPORTS_D2)
comp_array[i++] = "1=D2";
if (pwr_p->pwr_pmcaps & PCIE_SUPPORTS_D1)
comp_array[i++] = "2=D1";
comp_array[i++] = "3=Full Power D0";
/*
* Create pm-components property, if it does not exist already.
*/
if (ddi_prop_update_string_array(DDI_DEV_T_NONE, dip,
"pm-components", comp_array, i) != DDI_PROP_SUCCESS) {
DBG(DBG_PWR, dip, "could not create pm-components prop\n");
pci_config_teardown(&conf_hdl);
return (DDI_FAILURE);
}
return (pxb_pwr_init_and_raise(dip, pwr_p));
}
/*
* Initializes the power level and raise the power to D0, if it is
* not at D0.
*/
static int
pxb_pwr_init_and_raise(dev_info_t *dip, pcie_pwr_t *pwr_p)
{
uint16_t pmcsr;
int ret = DDI_SUCCESS;
/*
* Intialize our power level from PMCSR. The common code initializes
* this to UNKNOWN. There is no guarantee that we will be at full
* power at attach. If we are not at D0, raise the power.
*/
pmcsr = pci_config_get16(pwr_p->pwr_conf_hdl, pwr_p->pwr_pmcsr_offset);
pmcsr &= PCI_PMCSR_STATE_MASK;
switch (pmcsr) {
case PCI_PMCSR_D0:
pwr_p->pwr_func_lvl = PM_LEVEL_D0;
break;
case PCI_PMCSR_D1:
pwr_p->pwr_func_lvl = PM_LEVEL_D1;
break;
case PCI_PMCSR_D2:
pwr_p->pwr_func_lvl = PM_LEVEL_D2;
break;
case PCI_PMCSR_D3HOT:
pwr_p->pwr_func_lvl = PM_LEVEL_D3;
break;
default:
break;
}
/* Raise the power to D0. */
if (pwr_p->pwr_func_lvl != PM_LEVEL_D0 &&
((ret = pm_raise_power(dip, 0, PM_LEVEL_D0)) != DDI_SUCCESS)) {
/*
* Read PMCSR again. If it is at D0, ignore the return
* value from pm_raise_power.
*/
pmcsr = pci_config_get16(pwr_p->pwr_conf_hdl,
pwr_p->pwr_pmcsr_offset);
if ((pmcsr & PCI_PMCSR_STATE_MASK) == PCI_PMCSR_D0)
ret = DDI_SUCCESS;
else {
DBG(DBG_PWR, dip, "pxb_pwr_setup: could not raise "
"power to D0 \n");
}
}
if (ret == DDI_SUCCESS)
pwr_p->pwr_func_lvl = PM_LEVEL_D0;
return (ret);
}
static int
pxb_fm_init(pxb_devstate_t *pxb_p)
{
dev_info_t *dip = pxb_p->pxb_dip;
pxb_p->pxb_fm_cap = DDI_FM_EREPORT_CAPABLE | DDI_FM_ERRCB_CAPABLE |
DDI_FM_ACCCHK_CAPABLE | DDI_FM_DMACHK_CAPABLE;
/*
* Request our capability level and get our parents capability
* and ibc.
*/
ddi_fm_init(dip, &pxb_p->pxb_fm_cap, &pxb_p->pxb_fm_ibc);
pci_ereport_setup(dip);
/*
* Register error callback with our parent.
*/
ddi_fm_handler_register(pxb_p->pxb_dip, pxb_fm_err_callback,
(void *)&pxb_p->pxb_config_handle);
return (DDI_SUCCESS);
}
/*
* Breakdown our FMA resources
*/
static void
pxb_fm_fini(pxb_devstate_t *pxb_p)
{
dev_info_t *dip = pxb_p->pxb_dip;
/*
* Clean up allocated fm structures
*/
ddi_fm_handler_unregister(dip);
pci_ereport_teardown(dip);
ddi_fm_fini(dip);
}
/*
* Function used to initialize FMA for our children nodes. Called
* through pci busops when child node calls ddi_fm_init.
*/
/*ARGSUSED*/
int
pxb_fm_init_child(dev_info_t *dip, dev_info_t *cdip, int cap,
ddi_iblock_cookie_t *ibc_p)
{
pxb_devstate_t *pxb_p = (pxb_devstate_t *)
ddi_get_soft_state(pxb_state, ddi_get_instance(dip));
*ibc_p = pxb_p->pxb_fm_ibc;
return (pxb_p->pxb_fm_cap | DDI_FM_DMACHK_CAPABLE);
}
/*
* FMA Error callback handler.
* Need to revisit when pcie fm is supported.
*/
/*ARGSUSED*/
static int
pxb_fm_err_callback(dev_info_t *dip, ddi_fm_error_t *derr,
const void *impl_data)
{
pci_ereport_post(dip, derr, NULL);
return (derr->fme_status);
}
/*
* undo whatever is done in pxb_pwr_setup. called by pxb_detach()
*/
static void
pxb_pwr_teardown(dev_info_t *dip)
{
pcie_pwr_t *pwr_p;
if (!PCIE_PMINFO(dip) || !(pwr_p = PCIE_NEXUS_PMINFO(dip)))
return;
(void) ddi_prop_remove(DDI_DEV_T_NONE, dip, "pm-components");
if (pwr_p->pwr_conf_hdl)
pci_config_teardown(&pwr_p->pwr_conf_hdl);
}
/*ARGSUSED*/
static int pxb_pciehpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle)
{
uint16_t status;
uint8_t cap_ptr, cap_id;
status = pci_config_get16(config_handle, PCI_CONF_STAT);
if (!(status & PCI_STAT_CAP))
cap_ptr = PCI_CAP_NEXT_PTR_NULL;
else {
cap_ptr = pci_config_get8(config_handle, PCI_CONF_CAP_PTR);
cap_ptr &= 0xFC;
}
while (cap_ptr != PCI_CAP_NEXT_PTR_NULL) {
cap_id = pci_config_get8(config_handle, cap_ptr);
if (cap_id == PCI_CAP_ID_PCI_E) {
uint16_t slotimpl;
slotimpl = pci_config_get16(config_handle, cap_ptr +
PCIE_PCIECAP) & PCIE_PCIECAP_SLOT_IMPL;
if (slotimpl)
if (pci_config_get32(config_handle, cap_ptr +
PCIE_SLOTCAP) &
PCIE_SLOTCAP_HP_CAPABLE)
break;
}
cap_ptr = pci_config_get8(config_handle, cap_ptr +
PCI_CAP_NEXT_PTR);
cap_ptr &= 0xFC;
}
return ((cap_ptr != PCI_CAP_NEXT_PTR_NULL) ? DDI_SUCCESS : DDI_FAILURE);
}
/*ARGSUSED*/
static int pxb_pcishpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle)
{
uint16_t status;
uint8_t cap_ptr, cap_id;
status = pci_config_get16(config_handle, PCI_CONF_STAT);
if (!(status & PCI_STAT_CAP))
cap_ptr = PCI_CAP_NEXT_PTR_NULL;
else {
cap_ptr = pci_config_get8(config_handle, PCI_CONF_CAP_PTR);
cap_ptr &= 0xFC;
}
while (cap_ptr != PCI_CAP_NEXT_PTR_NULL) {
cap_id = pci_config_get8(config_handle, cap_ptr);
if (cap_id == PCI_CAP_ID_PCI_HOTPLUG)
break;
cap_ptr = pci_config_get8(config_handle, cap_ptr +
PCI_CAP_NEXT_PTR);
cap_ptr &= 0xFC;
}
return ((cap_ptr != PCI_CAP_NEXT_PTR_NULL) ? DDI_SUCCESS : DDI_FAILURE);
}
/* check if this device has PCIe link underneath. */
static int
ppb_pcie_device_type(pxb_devstate_t *pxb_p)
{
int port_type = pxb_p->pxb_port_type;
/* No PCIe CAP regs, we are not PCIe device_type */
if (port_type < 0)
return (DDI_FAILURE);
/* check for all PCIe device_types */
if ((port_type == PCIE_PCIECAP_DEV_TYPE_UP) ||
(port_type == PCIE_PCIECAP_DEV_TYPE_DOWN) ||
(port_type == PCIE_PCIECAP_DEV_TYPE_PCI2PCIE))
return (DDI_SUCCESS);
return (DDI_FAILURE);
}
#ifdef PRINT_PLX_SEEPROM_CRC
static void
pxb_print_plx_seeprom_crc_data(pxb_devstate_t *pxb_p)
{
ddi_acc_handle_t h;
dev_info_t *dip = pxb_p->pxb_dip;
int nregs;
caddr_t mp;
off_t bar_size;
ddi_device_acc_attr_t mattr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
uint32_t addr_reg_off = 0x260, data_reg_off = 0x264, data = 0x6BE4;
if (pxb_p->pxb_vendor_id != PXB_VENDOR_PLX)
return;
if (ddi_dev_nregs(dip, &nregs) != DDI_SUCCESS)
return;
if (nregs < 2) /* check for CONF entry only, no BARs */
return;
if (ddi_dev_regsize(dip, 1, &bar_size) != DDI_SUCCESS)
return;
if (ddi_regs_map_setup(dip, 1, (caddr_t *)&mp, 0, bar_size,
&mattr, &h) != DDI_SUCCESS)
return;
ddi_put32(h, (uint32_t *)((uchar_t *)mp + addr_reg_off), data);
delay(drv_usectohz(1000000));
printf("%s#%d: EEPROM StatusReg = %x, CRC = %x\n",
ddi_driver_name(dip), ddi_get_instance(dip),
ddi_get32(h, (uint32_t *)((uchar_t *)mp + addr_reg_off)),
ddi_get32(h, (uint32_t *)((uchar_t *)mp + data_reg_off)));
#ifdef PLX_HOT_RESET_DISABLE
/* prevent hot reset from propogating downstream. */
data = ddi_get32(h, (uint32_t *)((uchar_t *)mp + 0x1DC));
ddi_put32(h, (uint32_t *)((uchar_t *)mp + 0x1DC), data | 0x80000);
delay(drv_usectohz(1000000));
printf("%s#%d: EEPROM 0x1DC prewrite=%x postwrite=%x\n",
ddi_driver_name(dip), ddi_get_instance(dip), data,
ddi_get32(h, (uint32_t *)((uchar_t *)mp + 0x1DC)));
#endif
ddi_regs_map_free(&h);
}
#endif
/*
* given a cap_id, return its cap_id location in config space
*/
static int
pxb_get_cap(ddi_acc_handle_t config_handle, uint8_t cap_id)
{
uint8_t curcap;
uint_t cap_id_loc;
uint16_t status;
int location = -1;
/*
* Need to check the Status register for ECP support first.
* Also please note that for type 1 devices, the
* offset could change. Should support type 1 next.
*/
status = pci_config_get16(config_handle, PCI_CONF_STAT);
if (!(status & PCI_STAT_CAP)) {
return (-1);
}
cap_id_loc = pci_config_get8(config_handle, PCI_CONF_CAP_PTR);
/* Walk the list of capabilities */
while (cap_id_loc) {
curcap = pci_config_get8(config_handle, cap_id_loc);
if (curcap == cap_id) {
location = cap_id_loc;
break;
}
cap_id_loc = pci_config_get8(config_handle,
cap_id_loc + 1);
}
return (location);
}
static uint16_t
pxb_find_ext_cap_reg(ddi_acc_handle_t config_handle, uint16_t cap_id)
{
uint32_t hdr, hdr_next_ptr, hdr_cap_id;
uint16_t offset = P2ALIGN(PCIE_EXT_CAP, 4);
hdr = pci_config_get32(config_handle, offset);
hdr_next_ptr = (hdr >> PCIE_EXT_CAP_NEXT_PTR_SHIFT) &
PCIE_EXT_CAP_NEXT_PTR_MASK;
hdr_cap_id = (hdr >> PCIE_EXT_CAP_ID_SHIFT) &
PCIE_EXT_CAP_ID_MASK;
while ((hdr_next_ptr != PCIE_EXT_CAP_NEXT_PTR_NULL) &&
(hdr_cap_id != cap_id)) {
offset = P2ALIGN(hdr_next_ptr, 4);
hdr = pci_config_get32(config_handle, offset);
hdr_next_ptr = (hdr >> PCIE_EXT_CAP_NEXT_PTR_SHIFT) &
PCIE_EXT_CAP_NEXT_PTR_MASK;
hdr_cap_id = (hdr >> PCIE_EXT_CAP_ID_SHIFT) &
PCIE_EXT_CAP_ID_MASK;
}
if (hdr_cap_id == cap_id)
return (P2ALIGN(offset, 4));
return (PCIE_EXT_CAP_NEXT_PTR_NULL);
}
/* check for known bad functionality in devices */
static int
pxb_bad_func(pxb_devstate_t *pxb, int func)
{
int ret = B_FALSE;
switch (func) {
/* some devices do not render MSI well. */
case PXB_MSI:
if (pxb_check_bad_devs(pxb, PXB_VENDOR_SUN) ||
pxb_check_bad_devs(pxb, PXB_VENDOR_PLX)) {
if (pxb->pxb_rev_id <=
PXB_DEVICE_PLX_BAD_MSI_REV)
ret = B_TRUE;
}
break;
case PXB_LINK_INIT:
/*
* some devices require certain initialization sequence so
* as to not get wedged.
*/
case PXB_HOTPLUG_MSGS:
/*
* disable UR for 1.0a implementations where
* hotplug messages sent downstream cause URs to be
* detected causing error messages to be sent to the fabric.
*/
if (pxb_check_bad_devs(pxb, PXB_VENDOR_SUN) ||
pxb_check_bad_devs(pxb, PXB_VENDOR_PLX)) {
ret = B_TRUE;
}
if (func == PXB_HOTPLUG_MSGS)
ret = B_TRUE;
break;
default:
break;
}
return (ret);
}
/* check for known bad devices from a vendor */
static int
pxb_check_bad_devs(pxb_devstate_t *pxb, int vend)
{
int ret = B_FALSE;
switch (vend) {
case PXB_VENDOR_SUN:
if ((pxb->pxb_vendor_id == PXB_VENDOR_SUN) &&
((pxb->pxb_device_id ==
PXB_DEVICE_PLX_PCIX) ||
(pxb->pxb_device_id ==
PXB_DEVICE_PLX_PCIE))) {
ret = B_TRUE;
}
break;
case PXB_VENDOR_PLX:
if ((pxb->pxb_vendor_id == PXB_VENDOR_PLX) &&
((pxb->pxb_device_id ==
PXB_DEVICE_PLX_8532) ||
(pxb->pxb_device_id ==
PXB_DEVICE_PLX_8516))) {
ret = B_TRUE;
}
break;
default:
break;
}
return (ret);
}