pcieb.c revision 86a9c507121ec77a92601d8844a5ca82373cd4aa
/*
* 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
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Common x86 and SPARC PCI-E to PCI bus bridge nexus driver
*/
#include <sys/sysmacros.h>
#include <sys/autoconf.h>
#include <sys/ddi_impldefs.h>
#include <sys/pci_impl.h>
#include <sys/pcie_impl.h>
#include <sys/pcie_pwr.h>
#include "pcieb.h"
#ifdef PX_PLX
#endif /* PX_PLX */
/*LINTLIBRARY*/
/* panic flag */
int pcieb_die = PF_ERR_FATAL_FLAGS;
/* flag to turn on MSI support */
int pcieb_enable_msi = 1;
#if defined(DEBUG)
uint_t pcieb_dbg_print = 0;
static char *pcieb_debug_sym [] = { /* same sequence as pcieb_debug_bit */
/* 0 */ "attach",
/* 1 */ "pwr",
/* 2 */ "intr"
};
#endif /* DEBUG */
void *);
static struct bus_ops pcieb_bus_ops = {
0,
0,
0,
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)(); */
pcieb_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_exit)(); */
pcie_bus_power, /* (*bus_power)(); */
pcieb_intr_ops, /* (*bus_intr_op)(); */
pcie_hp_common_ops /* (*bus_hp_op)(); */
};
/* PM related functions */
/* Hotplug related functions */
/*
* soft state pointer
*/
void *pcieb_state;
static struct cb_ops pcieb_cb_ops = {
pcieb_open, /* open */
pcieb_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
pcieb_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
pcie_prop_op, /* cb_prop_op */
NULL, /* streamtab */
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
static int pcieb_probe(dev_info_t *);
DEVO_REV, /* devo_rev */
0, /* refcnt */
pcieb_info, /* info */
nulldev, /* identify */
pcieb_probe, /* probe */
pcieb_attach, /* attach */
pcieb_detach, /* detach */
nulldev, /* reset */
&pcieb_cb_ops, /* driver operations */
&pcieb_bus_ops, /* bus operations */
pcie_power, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
/*
* Module linkage information for the kernel.
*/
&mod_driverops, /* Type of module */
&pcieb_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
(void *)&modldrv,
};
/*
* forward function declarations:
*/
static void pcieb_uninitchild(dev_info_t *);
/* interrupt related declarations */
static int pcieb_msi_supported(dev_info_t *);
int
_init(void)
{
int e;
return (e);
}
int
_fini(void)
{
int e;
if ((e = mod_remove(&modlinkage)) == 0) {
}
return (e);
}
int
{
}
/* ARGSUSED */
static int
{
int ret = DDI_SUCCESS;
switch (infocmd) {
case DDI_INFO_DEVT2INSTANCE:
break;
case DDI_INFO_DEVT2DEVINFO:
ret = DDI_FAILURE;
break;
}
break;
default:
ret = DDI_FAILURE;
break;
}
return (ret);
}
/*ARGSUSED*/
static int
{
return (DDI_PROBE_SUCCESS);
}
static int
{
int instance;
char device_type[8];
switch (cmd) {
case DDI_RESUME:
(void) pcie_pwr_resume(devi);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
case DDI_ATTACH:
break;
}
if (!(PCIE_IS_BDG(bus_p))) {
" bridge\n");
return (DDI_FAILURE);
}
/*
* If PCIE_LINKCTL_LINK_DISABLE bit in the PCIe Config
* Space (PCIe Capability Link Control Register) is set,
* then do not bind the driver.
*/
return (DDI_FAILURE);
/*
* Allocate and get soft state structure.
*/
return (DDI_FAILURE);
goto fail;
}
(void *)pcieb->pcieb_fm_ibc);
(void *)pcieb->pcieb_fm_ibc);
/* create special properties for device identification */
/*
* is at D0 during attach.
*/
goto fail;
}
goto fail;
}
/*
* Make sure the "device_type" property exists.
*/
else
"device_type", device_type);
/*
* Check whether the "ranges" property is present.
* Otherwise create the ranges property by reading
* the configuration registers
*/
"ranges") == 0) {
}
if (PCIE_IS_PCI_BDG(bus_p))
#ifdef PX_PLX
#endif /* PX_PLX */
goto fail;
/*
* Initialize interrupt handlers. Ignore return value.
*/
(void) pcieb_intr_attach(pcieb);
(void) pcie_hpintr_enable(devi);
/* Do any platform specific workarounds needed at this time */
/*
* If this is a root port, determine and set the max payload size.
* Since this will involve scanning the fabric, all error enabling
* and sw workarounds should be in place before doing this.
*/
if (PCIE_IS_RP(bus_p))
return (DDI_SUCCESS);
fail:
return (DDI_FAILURE);
}
static int
{
int error = DDI_SUCCESS;
switch (cmd) {
case DDI_SUSPEND:
return (error);
case DDI_DETACH:
break;
default:
return (DDI_FAILURE);
}
/* disable hotplug interrupt */
(void) pcie_hpintr_disable(devi);
/* remove interrupt handlers */
/* uninitialize inband PCI-E HPC if present */
(void) pcie_uninit(devi);
"pcie_ce_mask");
/*
* And finally free the per-pci soft state.
*/
return (DDI_SUCCESS);
}
static int
{
}
}
static int
{
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);
return (DDI_SUCCESS);
case DDI_CTLOPS_INITCHILD:
case DDI_CTLOPS_UNINITCHILD:
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_PEEK:
case DDI_CTLOPS_POKE:
case DDI_CTLOPS_ATTACH:
return (DDI_SUCCESS);
case DDI_PRE:
return (DDI_FAILURE);
}
return (pcie_pm_hold(dip));
return (DDI_SUCCESS);
case DDI_POST:
/*
* Attach failed for the child device. The child
* driver may have made PM calls before the
* attach failed. pcie_pm_remove_child() should
* cleanup PM state and holds (if any)
* associated with the child device.
*/
}
}
/*
* For empty hotplug-capable slots, we should explicitly
* disable the errors, so that we won't panic upon
* unsupported hotplug messages.
*/
DDI_PROP_DONTPASS, "hotplug-capable")) ||
ddi_get_child(rdip)) {
(void) pcie_postattach_child(rdip);
return (DDI_SUCCESS);
}
return (DDI_SUCCESS);
default:
break;
}
return (DDI_SUCCESS);
case DDI_CTLOPS_DETACH:
return (DDI_SUCCESS);
case DDI_PRE:
return (DDI_SUCCESS);
case DDI_POST:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
default:
break;
}
return (DDI_SUCCESS);
default:
}
*(int *)result = 0;
®len) != DDI_SUCCESS)
return (DDI_FAILURE);
if (ctlop == DDI_CTLOPS_NREGS)
else if (ctlop == DDI_CTLOPS_REGSIZE) {
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/*
* 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
{
char **unit_addr;
uint_t n;
/*
* For .conf nodes, use unit-address property as name
*/
if (ndi_dev_is_persistent_node(child) == 0) {
"cannot find unit-address in %s.conf",
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Get the address portion of the node name based on
* the function and device number.
*/
return (DDI_FAILURE);
}
/* copy the device identifications */
== PCIE_ARI_FORW_ENABLED) {
device = 0;
}
if (func != 0)
else
return (DDI_SUCCESS);
}
static int
{
char name[MAXNAMELEN];
int result = DDI_FAILURE;
/*
* Name the child
*/
goto done;
}
/*
* 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.
*/
/*
* Merged ok - return failure to remove the node.
*/
goto done;
}
/* workaround for ddivs to run under PCI-E */
if (pci_allow_pseudo_children) {
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.
*/
goto done;
}
/* platform specific initchild */
"INITCHILD: px_pm_hold failed\n");
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.
*/
"config-regs-saved-by-child") == 1) {
"INITCHILD: config regs to be restored by child"
goto cleanup;
}
"INITCHILD: config regs setup for %s@%s\n",
goto cleanup;
}
#ifdef PX_PLX
goto cleanup;
}
#endif /* PX_PLX */
done:
return (result);
}
static void
{
/*
* Strip the node to properly convert it back to prototype form
*/
}
static boolean_t
{
return (B_TRUE);
return (B_FALSE);
}
static int
{
int intr_types;
/* Allow platform specific code to do any initialization first */
/*
* 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.
*/
" failed\n");
goto FAIL;
}
if ((intr_types & DDI_INTR_TYPE_MSI) &&
else {
" handler\n");
}
}
if (intr_types != DDI_INTR_TYPE_MSI) {
/*
* MSIs are not supported or MSI initialization failed. For Root
* Ports mark this so error handling might try to fallback to
* some other mechanism if available (machinecheck etc.).
*/
}
if (intr_types & DDI_INTR_TYPE_FIXED) {
DDI_SUCCESS) {
"Unable to attach INTx handler\n");
goto FAIL;
}
}
return (DDI_SUCCESS);
FAIL:
return (DDI_FAILURE);
}
/*
* 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
{
int intr_cap = 0;
int inum = 0;
int ret, hp_msi_off;
request = 0;
if (PCIE_IS_HOTPLUG_ENABLED(dip)) {
request++;
}
/*
* Hotplug and PME share the same MSI vector. If hotplug is not
* supported check if MSI is needed for PME.
*/
(vendorid == NVIDIA_VENDOR_ID)) {
if (!is_hp)
request++;
}
/*
* Setup MSI if this device is a Rootport and has AER. Currently no
* SPARC Root Port supports fabric errors being reported through it.
*/
if (intr_type == DDI_INTR_TYPE_MSI) {
request++;
}
if (request == 0)
return (DDI_SUCCESS);
/*
* Get number of supported interrupts.
*
* in a FAILURE, if the device is not configured in a way that
* interrupts are needed. (eg. hotplugging)
*/
return (DDI_FAILURE);
}
/* Allocate an array of interrupt handlers */
KM_SLEEP);
goto FAIL;
}
/* Save the actual number of interrupts allocated */
}
/*
* NVidia (MCP55 and other) chipsets have a errata that if the number
* of requested MSI intrs is not allocated we have to fall back to INTx.
*/
if (intr_type == DDI_INTR_TYPE_MSI) {
goto FAIL;
}
}
/* Get interrupt priority */
if (ret != DDI_SUCCESS) {
ret);
goto FAIL;
}
if (ret != DDI_SUCCESS) {
" %d\n", ret);
goto FAIL;
}
}
if (ret != DDI_SUCCESS) {
"interrupt(%d)\n", ret);
break;
}
}
/* If unsucessful, remove the added handlers */
if (ret != DDI_SUCCESS) {
for (x = 0; x < count; x++) {
}
goto FAIL;
}
/*
* Get this intr lock because we are not quite ready to handle
* interrupts immediately after enabling it. The MSI multi register
* gets programmed in ddi_intr_enable after which we need to get the
*/
if (intr_cap & DDI_INTR_FLAG_BLOCK) {
} else {
}
}
/* Save the interrupt type */
if (intr_type == DDI_INTR_TYPE_MSI) {
if (hp_msi_off >= count) {
goto FAIL;
}
if (is_hp)
if (is_pme)
} else {
/* INTx handles only Hotplug interrupts */
if (is_hp)
}
/*
* Get the MSI offset for errors from the AER Root Error status
* register.
*/
if (PCIE_HAS_AER(bus_p)) {
int aer_msi_off;
if (aer_msi_off >= count) {
" AER cap > max allocated %d\n",
aer_msi_off, count);
goto FAIL;
}
} else {
/*
* This RP does not have AER. Fallback to the
* SERR+Machinecheck approach if available.
*/
}
}
return (DDI_SUCCESS);
FAIL:
return (DDI_FAILURE);
}
static void
{
int x;
if ((flags & PCIEB_INIT_ENABLE) &&
(flags & PCIEB_INIT_BLOCK)) {
flags &= ~(PCIEB_INIT_ENABLE |
}
if (flags & PCIEB_INIT_MUTEX)
for (x = 0; x < count; x++) {
if (flags & PCIEB_INIT_ENABLE)
if (flags & PCIEB_INIT_HANDLER)
if (flags & PCIEB_INIT_ALLOC)
}
if (flags & PCIEB_INIT_HTABLE)
flags &= ~PCIEB_INIT_HTABLE;
}
/*
* Checks if this device needs MSIs enabled or not.
*/
/*ARGSUSED*/
static int
{
}
/*ARGSUSED*/
static int
{
}
static int
{
int fm_cap = DDI_FM_EREPORT_CAPABLE;
/*
* Request our capability level and get our parents capability
* and ibc.
*/
return (DDI_SUCCESS);
}
/*
* Breakdown our FMA resources
*/
static void
{
/*
* Clean up allocated fm structures
*/
}
static int
{
int rv;
return (ENXIO);
return (rv);
}
static int
{
int rv;
return (ENXIO);
return (rv);
}
static int
int *rvalp)
{
int rv;
return (ENXIO);
/* To handle devctl and hotplug related ioctls */
return (rv);
}
/*
* Common interrupt handler for hotplug, PME and errors.
*/
static uint_t
{
int sts = 0;
int ret = DDI_INTR_UNCLAIMED;
int isrc;
goto FAIL;
if (isrc == PCIEB_INTR_SRC_UNKNOWN)
goto FAIL;
if (isrc & PCIEB_INTR_SRC_HP)
if (isrc & PCIEB_INTR_SRC_PME)
/* AER Error */
if (isrc & PCIEB_INTR_SRC_AER) {
/*
* Status Reg before claiming it. For now it's ok since
* we know we get 2 MSIs.
*/
fm_panic("%s-%d: PCI(-X) Express Fatal Error. (0x%x)",
}
FAIL:
return (ret);
}
/*
* Some PCI-X to PCI-E bridges do not support full 64-bit addressing on the
* PCI-X side of the bridge. We build a special version of this driver for
* to define the range of values which the chip can handle. The code below
* then clamps the DMA address range supplied by the driver, preventing the
* PCI-E nexus driver from allocating any memory the bridge can't deal
* with.
*/
static int
{
int ret;
#ifdef BCM_SW_WORKAROUNDS
/*
* If the leaf device's limits are outside than what the Broadcom
* bridge can handle, we need to clip the values passed up the chain.
*/
#endif /* BCM_SW_WORKAROUNDS */
/*
* This is a software workaround to fix the Broadcom 5714/5715 PCIe-PCI
* bridge prefetch bug. Intercept the DMA alloc handle request and set
* PX_DMAI_FLAGS_MAP_BUFZONE flag in the handle. If this flag is set,
* the px nexus driver will allocate an extra page & make it valid one,
* for any DVMA request that comes from any of the Broadcom bridge child
* devices.
*/
handlep)) == DDI_SUCCESS) {
#ifdef BCM_SW_WORKAROUNDS
#endif /* BCM_SW_WORKAROUNDS */
/*
* For a given rdip, update mp->dmai_bdf with the bdf value
* of pcieb's immediate child or secondary bus-id of the
* PCIe2PCI bridge.
*/
}
return (ret);
}
/*
* FDVMA feature is not supported for any child device of Broadcom 5714/5715
* PCIe-PCI bridge due to prefetch bug. Return failure immediately, so that
* these drivers will switch to regular DVMA path.
*/
/*ARGSUSED*/
static int
{
int ret;
#ifdef BCM_SW_WORKAROUNDS
if (cmd == DDI_DMA_RESERVE)
return (DDI_FAILURE);
#endif /* BCM_SW_WORKAROUNDS */
/*
* For a given rdip, update mp->dmai_bdf with the bdf value
* of pcieb's immediate child or secondary bus-id of the
* PCIe2PCI bridge.
*/
}
return (ret);
}
static int
{
}
/*
* Power management related initialization specific to pcieb.
* Called by pcieb_attach()
*/
static int
{
char *comp_array[5];
int i;
if (pcieb_plat_pwr_disable(dip)) {
(void) pcieb_pwr_disable(dip);
return (DDI_SUCCESS);
}
/* Code taken from pci_pci driver */
"failed\n");
return (DDI_FAILURE);
}
/*
* Walk the capabilities searching for a PM entry.
*/
DDI_FAILURE) {
" PCI PM data structure not found in config header\n");
return (DDI_SUCCESS);
}
/*
* Save offset to pmcsr for future references.
*/
if (pmcap & PCI_PMCAP_D1) {
}
if (pmcap & PCI_PMCAP_D2) {
}
i = 0;
comp_array[i++] = "NAME=PCIe switch/bridge PM";
comp_array[i++] = "0=Power Off (D3)";
comp_array[i++] = "1=D2";
comp_array[i++] = "2=D1";
comp_array[i++] = "3=Full Power D0";
/*
* Create pm-components property, if it does not exist already.
*/
" prop\n");
return (DDI_FAILURE);
}
}
/*
* undo whatever is done in pcieb_pwr_setup. called by pcieb_detach()
*/
static void
{
return;
if (pwr_p->pwr_conf_hdl)
}
/*
* Initializes the power level and raise the power to D0, if it is
* not at D0.
*/
static int
{
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.
*/
switch (pmcsr) {
case PCI_PMCSR_D0:
break;
case PCI_PMCSR_D1:
break;
case PCI_PMCSR_D2:
break;
case PCI_PMCSR_D3HOT:
break;
default:
break;
}
/* Raise the power to D0. */
/*
* Read PMCSR again. If it is at D0, ignore the return
* value from pm_raise_power.
*/
ret = DDI_SUCCESS;
else {
"raise power to D0 \n");
}
}
if (ret == DDI_SUCCESS)
return (ret);
}
/*
* Disable PM for x86 and PLX 8532 switch.
* For PLX 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.
*/
static int
{
return (DDI_SUCCESS);
}
#ifdef DEBUG
int pcieb_dbg_intr_print = 0;
void
{
if (!pcieb_dbg_print)
return;
if (dip)
if (servicing_interrupt()) {
if (pcieb_dbg_intr_print)
} else {
}
}
#endif
static void
{
/*
* Identify first in chassis. In the special case of a Sun branded
* PLX device, it obviously is first in chassis. Otherwise, in the
* general case, look for an Expansion Slot Register and check its
* first-in-chassis bit.
*/
#ifdef PX_PLX
if ((vendor_id == PXB_VENDOR_SUN) &&
((device_id == PXB_DEVICE_PLX_PCIX) ||
(device_id == PXB_DEVICE_PLX_PCIE))) {
fic = 1;
}
#endif /* PX_PLX */
if (PCI_CAPSLOT_FIC(esr))
fic = 1;
}
if ((PCI_CAP_LOCATE(config_handle,
/* Serialid can be 0 thru a full 40b number */
serialid <<= 32;
}
if (fic)
"first-in-chassis");
if (serialid)
"serialid#", serialid);
}
static void
{
int i = 0, rangelen = sizeof (ppb_ranges_t)/sizeof (int);
/*
* Create ranges for IO space
*/
(PCI_REG_REL_M | PCI_ADDR_IO);
}
}
}
/*
* Create ranges for 32bit memory space
*/
i++;
}
if (i) {
}
}
/*
* For PCI and PCI-X devices including PCIe2PCI bridge, initialize
* cache-line-size and latency timer configuration registers.
*/
void
{
uint_t n;
/* Initialize cache-line-size configuration register if needed */
"cache-line-size", 0) == 0) {
if (n != 0) {
"cache-line-size", n);
}
}
/* Initialize latency timer configuration registers if needed */
"latency-timer", 0) == 0) {
/* Determine the configuration header type */
} else {
}
if (n != 0) {
"latency-timer", n);
}
}
}