summaryrefslogtreecommitdiff
path: root/usr/src/uts/sun4u/io/pci/pci_util.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/sun4u/io/pci/pci_util.c')
-rw-r--r--usr/src/uts/sun4u/io/pci/pci_util.c906
1 files changed, 906 insertions, 0 deletions
diff --git a/usr/src/uts/sun4u/io/pci/pci_util.c b/usr/src/uts/sun4u/io/pci/pci_util.c
new file mode 100644
index 0000000000..421b322038
--- /dev/null
+++ b/usr/src/uts/sun4u/io/pci/pci_util.c
@@ -0,0 +1,906 @@
+/*
+ * 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"
+
+/*
+ * PCI nexus utility routines:
+ * property and config routines for attach()
+ * reg/intr/range/assigned-address property routines for bus_map()
+ * init_child()
+ * fault handling
+ */
+
+#include <sys/types.h>
+#include <sys/kmem.h>
+#include <sys/async.h>
+#include <sys/sysmacros.h>
+#include <sys/sunddi.h>
+#include <sys/sunndi.h>
+#include <sys/fm/protocol.h>
+#include <sys/fm/io/pci.h>
+#include <sys/fm/util.h>
+#include <sys/ddi_impldefs.h>
+#include <sys/pci/pci_obj.h>
+
+/*LINTLIBRARY*/
+
+/*
+ * get_pci_properties
+ *
+ * This function is called from the attach routine to get the key
+ * properties of the pci nodes.
+ *
+ * used by: pci_attach()
+ *
+ * return value: DDI_FAILURE on failure
+ */
+int
+get_pci_properties(pci_t *pci_p, dev_info_t *dip)
+{
+ int i;
+
+ /*
+ * Get the device's port id.
+ */
+ if ((pci_p->pci_id = (uint32_t)pci_get_portid(dip)) == -1u) {
+ cmn_err(CE_WARN, "%s%d: no portid property\n",
+ ddi_driver_name(dip), ddi_get_instance(dip));
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Get the bus-ranges property.
+ */
+ i = sizeof (pci_p->pci_bus_range);
+ if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+ "bus-range", (caddr_t)&pci_p->pci_bus_range, &i) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "%s%d: no bus-range property\n",
+ ddi_driver_name(dip), ddi_get_instance(dip));
+ return (DDI_FAILURE);
+ }
+ DEBUG2(DBG_ATTACH, dip, "get_pci_properties: bus-range (%x,%x)\n",
+ pci_p->pci_bus_range.lo, pci_p->pci_bus_range.hi);
+
+ /*
+ * disable streaming cache if necessary, this must be done
+ * before PBM is configured.
+ */
+ if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+ "no-streaming-cache")) {
+ pci_stream_buf_enable = 0;
+ pci_stream_buf_exists = 0;
+ }
+
+ /*
+ * Get the ranges property.
+ */
+ if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "ranges",
+ (caddr_t)&pci_p->pci_ranges, &pci_p->pci_ranges_length) !=
+ DDI_SUCCESS) {
+
+ cmn_err(CE_WARN, "%s%d: no ranges property\n",
+ ddi_driver_name(dip), ddi_get_instance(dip));
+ return (DDI_FAILURE);
+ }
+ pci_fix_ranges(pci_p->pci_ranges,
+ pci_p->pci_ranges_length / sizeof (pci_ranges_t));
+
+ /*
+ * Determine the number upa slot interrupts.
+ */
+ pci_p->pci_numproxy = pci_get_numproxy(pci_p->pci_dip);
+ DEBUG1(DBG_ATTACH, dip, "get_pci_properties: numproxy=%d\n",
+ pci_p->pci_numproxy);
+
+ pci_p->pci_thermal_interrupt =
+ ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
+ "thermal-interrupt", -1);
+ DEBUG1(DBG_ATTACH, dip, "get_pci_properties: thermal_interrupt=%d\n",
+ pci_p->pci_thermal_interrupt);
+ return (DDI_SUCCESS);
+}
+
+/*
+ * free_pci_properties:
+ *
+ * This routine frees the memory used to cache the
+ * "ranges" properties of the pci bus device node.
+ *
+ * used by: pci_detach()
+ *
+ * return value: none
+ */
+void
+free_pci_properties(pci_t *pci_p)
+{
+ kmem_free(pci_p->pci_ranges, pci_p->pci_ranges_length);
+}
+
+/*
+ * pci_reloc_reg
+ *
+ * If the "reg" entry (*pci_rp) is relocatable, lookup "assigned-addresses"
+ * property to fetch corresponding relocated address.
+ *
+ * used by: pci_map()
+ *
+ * return value:
+ *
+ * DDI_SUCCESS - on success
+ * DDI_ME_INVAL - regspec is invalid
+ */
+int
+pci_reloc_reg(dev_info_t *dip, dev_info_t *rdip, pci_t *pci_p,
+ pci_regspec_t *rp)
+{
+ int assign_len, assign_entries, i;
+ pci_regspec_t *assign_p;
+ register uint32_t phys_hi = rp->pci_phys_hi;
+ register uint32_t mask = PCI_REG_ADDR_M | PCI_CONF_ADDR_MASK;
+ register uint32_t phys_addr = phys_hi & mask;
+
+ DEBUG5(DBG_MAP | DBG_CONT, dip, "\tpci_reloc_reg fr: %x.%x.%x %x.%x\n",
+ rp->pci_phys_hi, rp->pci_phys_mid, rp->pci_phys_low,
+ rp->pci_size_hi, rp->pci_size_low);
+
+ if ((phys_hi & PCI_RELOCAT_B) || !(phys_hi & PCI_ADDR_MASK))
+ return (DDI_SUCCESS);
+
+ if (pci_p->hotplug_capable == B_FALSE) { /* validate bus # */
+ uint32_t bus = PCI_REG_BUS_G(phys_hi);
+ if (bus < pci_p->pci_bus_range.lo ||
+ bus > pci_p->pci_bus_range.hi) {
+ DEBUG1(DBG_MAP | DBG_CONT, dip, "bad bus# (%x)\n", bus);
+ return (DDI_ME_INVAL);
+ }
+ }
+
+ /* phys_mid must be 0 regardless space type. */
+ if (rp->pci_phys_mid != 0 || rp->pci_size_hi != 0) {
+ DEBUG0(DBG_MAP | DBG_CONT, pci_p->pci_dip,
+ "phys_mid or size_hi not 0\n");
+ return (DDI_ME_INVAL);
+ }
+
+ if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS,
+ "assigned-addresses", (caddr_t)&assign_p, &assign_len))
+ return (DDI_ME_INVAL);
+
+ assign_entries = assign_len / sizeof (pci_regspec_t);
+ for (i = 0; i < assign_entries; i++, assign_p++) {
+ if ((assign_p->pci_phys_hi & mask) == phys_addr) {
+ rp->pci_phys_low += assign_p->pci_phys_low;
+ break;
+ }
+ }
+ kmem_free(assign_p - i, assign_len);
+ DEBUG5(DBG_MAP | DBG_CONT, dip, "\tpci_reloc_reg to: %x.%x.%x %x.%x\n",
+ rp->pci_phys_hi, rp->pci_phys_mid, rp->pci_phys_low,
+ rp->pci_size_hi, rp->pci_size_low);
+ return (i < assign_entries ? DDI_SUCCESS : DDI_ME_INVAL);
+}
+
+/*
+ * use "ranges" to translate relocated pci regspec into parent space
+ */
+int
+pci_xlate_reg(pci_t *pci_p, pci_regspec_t *pci_rp, struct regspec *new_rp)
+{
+ int n;
+ pci_ranges_t *rng_p = pci_p->pci_ranges;
+ int rng_n = pci_p->pci_ranges_length / sizeof (pci_ranges_t);
+
+ uint32_t space_type = PCI_REG_ADDR_G(pci_rp->pci_phys_hi);
+ uint32_t reg_end, reg_begin = pci_rp->pci_phys_low;
+ uint32_t sz = pci_rp->pci_size_low;
+
+ uint32_t rng_begin, rng_end;
+
+ if (space_type == PCI_REG_ADDR_G(PCI_ADDR_CONFIG)) {
+ if (reg_begin > PCI_CONF_HDR_SIZE)
+ return (DDI_ME_INVAL);
+ sz = sz ? MIN(sz, PCI_CONF_HDR_SIZE) : PCI_CONF_HDR_SIZE;
+ reg_begin += pci_rp->pci_phys_hi;
+ }
+ reg_end = reg_begin + sz - 1;
+
+ for (n = 0; n < rng_n; n++, rng_p++) {
+ if (space_type != PCI_REG_ADDR_G(rng_p->child_high))
+ continue; /* not the same space type */
+
+ rng_begin = rng_p->child_low;
+ if (space_type == PCI_REG_ADDR_G(PCI_ADDR_CONFIG))
+ rng_begin += rng_p->child_high;
+
+ rng_end = rng_begin + rng_p->size_low - 1;
+ if (reg_begin >= rng_begin && reg_end <= rng_end)
+ break;
+ }
+ if (n >= rng_n)
+ return (DDI_ME_REGSPEC_RANGE);
+
+ new_rp->regspec_addr = reg_begin - rng_begin + rng_p->parent_low;
+ new_rp->regspec_bustype = rng_p->parent_high;
+ new_rp->regspec_size = sz;
+ DEBUG4(DBG_MAP | DBG_CONT, pci_p->pci_dip,
+ "\tpci_xlate_reg: entry %d new_rp %x.%x %x\n",
+ n, new_rp->regspec_bustype, new_rp->regspec_addr, sz);
+
+ return (DDI_SUCCESS);
+}
+
+
+/*
+ * report_dev
+ *
+ * This function is called from our control ops routine on a
+ * DDI_CTLOPS_REPORTDEV request.
+ *
+ * The display format is
+ *
+ * <name><inst> at <pname><pinst> device <dev> function <func>
+ *
+ * where
+ *
+ * <name> this device's name property
+ * <inst> this device's instance number
+ * <name> parent device's name property
+ * <inst> parent device's instance number
+ * <dev> this device's device number
+ * <func> this device's function number
+ */
+int
+report_dev(dev_info_t *dip)
+{
+ if (dip == (dev_info_t *)0)
+ return (DDI_FAILURE);
+ cmn_err(CE_CONT, "?PCI-device: %s@%s, %s%d\n",
+ ddi_node_name(dip), ddi_get_name_addr(dip),
+ ddi_driver_name(dip),
+ ddi_get_instance(dip));
+ return (DDI_SUCCESS);
+}
+
+
+/*
+ * reg property for pcimem nodes that covers the entire address
+ * space for the node: config, io, or memory.
+ */
+pci_regspec_t pci_pcimem_reg[3] =
+{
+ {PCI_ADDR_CONFIG, 0, 0, 0, 0x800000 },
+ {(uint_t)(PCI_ADDR_IO|PCI_RELOCAT_B), 0, 0, 0, PCI_IO_SIZE },
+ {(uint_t)(PCI_ADDR_MEM32|PCI_RELOCAT_B), 0, 0, 0, PCI_MEM_SIZE }
+};
+
+/*
+ * 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
+name_child(dev_info_t *child, char *name, int namelen)
+{
+ pci_regspec_t *pci_rp;
+ int reglen;
+ uint_t func;
+ char **unit_addr;
+ uint_t n;
+
+ /*
+ * Set the address portion of the node name based on
+ * unit-address property, if it exists.
+ * The interpretation of the unit-address is DD[,F]
+ * where DD is the device id and F is the function.
+ */
+ if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, child,
+ DDI_PROP_DONTPASS, "unit-address", &unit_addr, &n) ==
+ DDI_PROP_SUCCESS) {
+ 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);
+ }
+
+ /*
+ * The unit-address property is does not exist. Set 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, (uint_t *)&reglen) == DDI_SUCCESS) {
+ if (((reglen * sizeof (int)) % sizeof (pci_regspec_t)) != 0) {
+ cmn_err(CE_WARN, "reg property not well-formed");
+ return (DDI_FAILURE);
+ }
+
+ func = PCI_REG_FUNC_G(pci_rp[0].pci_phys_hi);
+ if (func != 0)
+ (void) snprintf(name, namelen, "%x,%x",
+ PCI_REG_DEV_G(pci_rp[0].pci_phys_hi), func);
+ else
+ (void) snprintf(name, namelen, "%x",
+ PCI_REG_DEV_G(pci_rp[0].pci_phys_hi));
+ ddi_prop_free(pci_rp);
+ return (DDI_SUCCESS);
+ }
+
+ cmn_err(CE_WARN, "cannot name pci child '%s'", ddi_node_name(child));
+ return (DDI_FAILURE);
+}
+
+int
+uninit_child(pci_t *pci_p, dev_info_t *child)
+{
+ DEBUG2(DBG_CTLOPS, pci_p->pci_dip,
+ "DDI_CTLOPS_UNINITCHILD: arg=%s%d\n",
+ ddi_driver_name(child), ddi_get_instance(child));
+
+
+ (void) pm_uninit_child(child);
+
+ ddi_set_name_addr(child, NULL);
+ ddi_remove_minor_node(child, NULL);
+ impl_rem_dev_props(child);
+
+ DEBUG0(DBG_PWR, ddi_get_parent(child), "\n\n");
+
+ /*
+ * Handle chip specific post-uninit-child tasks.
+ */
+ pci_post_uninit_child(pci_p);
+
+ return (DDI_SUCCESS);
+}
+
+/*
+ * init_child
+ *
+ * This function is called from our control ops routine on a
+ * DDI_CTLOPS_INITCHILD request. It builds and sets the device's
+ * parent private data area.
+ *
+ * used by: pci_ctlops()
+ *
+ * return value: none
+ */
+int
+init_child(pci_t *pci_p, dev_info_t *child)
+{
+ pci_regspec_t *pci_rp;
+ char name[10];
+ ddi_acc_handle_t config_handle;
+ uint16_t command_preserve, command;
+ uint8_t bcr;
+ uint8_t header_type, min_gnt;
+ uint16_t latency_timer;
+ uint_t n;
+ int i, no_config;
+
+ /*
+ * The following is a special case for pcimem nodes.
+ * For these nodes we create a reg property with a
+ * single entry that covers the entire address space
+ * for the node (config, io or memory).
+ */
+ if (strcmp(ddi_driver_name(child), "pcimem") == 0) {
+ (void) ddi_prop_create(DDI_DEV_T_NONE, child,
+ DDI_PROP_CANSLEEP, "reg", (caddr_t)pci_pcimem_reg,
+ sizeof (pci_pcimem_reg));
+ ddi_set_name_addr(child, "0");
+ ddi_set_parent_data(child, NULL);
+ return (DDI_SUCCESS);
+ }
+
+ /*
+ * Check whether the node has config space or is a hard decode
+ * node (possibly created by a driver.conf file).
+ */
+ no_config = ddi_prop_get_int(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
+ "no-config", 0);
+
+ /*
+ * Pseudo nodes indicate a prototype node with per-instance
+ * properties to be merged into the real h/w device node.
+ * However, do not merge if the no-config property is set
+ * (see PSARC 2000/088).
+ */
+ if ((ndi_dev_is_persistent_node(child) == 0) && (no_config == 0)) {
+ extern int pci_allow_pseudo_children;
+
+ if (ddi_getlongprop(DDI_DEV_T_ANY, child,
+ DDI_PROP_DONTPASS, "reg", (caddr_t)&pci_rp, &i) ==
+ DDI_SUCCESS) {
+ cmn_err(CE_WARN, "cannot merge prototype from %s.conf",
+ ddi_driver_name(child));
+ kmem_free(pci_rp, i);
+ return (DDI_NOT_WELL_FORMED);
+ }
+ /*
+ * Name the child
+ */
+ if (name_child(child, name, 10) != DDI_SUCCESS)
+ return (DDI_FAILURE);
+
+ ddi_set_name_addr(child, name);
+ ddi_set_parent_data(child, NULL);
+
+ /*
+ * Try to merge the properties from this prototype
+ * node into real h/w nodes.
+ */
+ if (ndi_merge_node(child, name_child) == DDI_SUCCESS) {
+ /*
+ * Merged ok - return failure to remove the node.
+ */
+ ddi_set_name_addr(child, NULL);
+ return (DDI_FAILURE);
+ }
+
+ /* workaround for ddivs to run under PCI */
+ if (pci_allow_pseudo_children)
+ return (DDI_SUCCESS);
+
+ cmn_err(CE_WARN, "!%s@%s: %s.conf properties not merged",
+ ddi_driver_name(child), ddi_get_name_addr(child),
+ ddi_driver_name(child));
+ ddi_set_name_addr(child, NULL);
+ return (DDI_NOT_WELL_FORMED);
+ }
+
+ if (name_child(child, name, 10) != DDI_SUCCESS)
+ return (DDI_FAILURE);
+ ddi_set_name_addr(child, name);
+
+ if (no_config != 0) {
+ /*
+ * There is no config space so there's nothing more to do.
+ */
+ return (DDI_SUCCESS);
+ }
+
+ if (pm_init_child(child) != DDI_SUCCESS)
+ return (DDI_FAILURE);
+
+
+ /*
+ * If configuration registers were previously saved by
+ * child (before it went to 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) {
+ DEBUG0(DBG_PWR, child,
+ "INITCHILD: config regs to be restored by child\n");
+
+ return (DDI_SUCCESS);
+ }
+
+ DEBUG2(DBG_PWR, ddi_get_parent(child),
+ "INITCHILD: config regs setup for %s@%s\n",
+ ddi_node_name(child), ddi_get_name_addr(child));
+
+ /*
+ * Map the child configuration space to for initialization.
+ * We assume the obp will do the following in the devices
+ * config space:
+ *
+ * Set the latency-timer register to values appropriate
+ * for the devices on the bus (based on other devices
+ * MIN_GNT and MAX_LAT registers.
+ *
+ * Set the fast back-to-back enable bit in the command
+ * register if it's supported and all devices on the bus
+ * have the capability.
+ *
+ */
+ if (pci_config_setup(child, &config_handle) != DDI_SUCCESS) {
+ (void) pm_uninit_child(child);
+ ddi_set_name_addr(child, NULL);
+
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Determine the configuration header type.
+ */
+ header_type = pci_config_get8(config_handle, PCI_CONF_HEADER);
+ DEBUG2(DBG_INIT_CLD, pci_p->pci_dip, "%s: header_type=%x\n",
+ ddi_driver_name(child), header_type);
+
+ /*
+ * Support for "command-preserve" property. Note that we
+ * add PCI_COMM_BACK2BACK_ENAB to the bits to be preserved
+ * since the obp will set this if the device supports and
+ * all targets on the same bus support it. Since psycho
+ * doesn't support PCI_COMM_BACK2BACK_ENAB, it will never
+ * be set. This is just here in case future revs do support
+ * PCI_COMM_BACK2BACK_ENAB.
+ */
+ command_preserve =
+ ddi_prop_get_int(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
+ "command-preserve", 0);
+ DEBUG2(DBG_INIT_CLD, pci_p->pci_dip, "%s: command-preserve=%x\n",
+ ddi_driver_name(child), command_preserve);
+ command = pci_config_get16(config_handle, PCI_CONF_COMM);
+ command &= (command_preserve | PCI_COMM_BACK2BACK_ENAB);
+ command |= (pci_command_default & ~command_preserve);
+ pci_config_put16(config_handle, PCI_CONF_COMM, command);
+ command = pci_config_get16(config_handle, PCI_CONF_COMM);
+ DEBUG2(DBG_INIT_CLD, pci_p->pci_dip, "%s: command=%x\n",
+ ddi_driver_name(child),
+ pci_config_get16(config_handle, PCI_CONF_COMM));
+
+ /*
+ * If the device has a bus control register then program it
+ * based on the settings in the command register.
+ */
+ if ((header_type & PCI_HEADER_TYPE_M) == PCI_HEADER_ONE) {
+ bcr = pci_config_get8(config_handle, PCI_BCNF_BCNTRL);
+ if (pci_command_default & PCI_COMM_PARITY_DETECT)
+ bcr |= PCI_BCNF_BCNTRL_PARITY_ENABLE;
+ if (pci_command_default & PCI_COMM_SERR_ENABLE)
+ bcr |= PCI_BCNF_BCNTRL_SERR_ENABLE;
+ bcr |= PCI_BCNF_BCNTRL_MAST_AB_MODE;
+ pci_config_put8(config_handle, PCI_BCNF_BCNTRL, bcr);
+ }
+
+ /*
+ * Initialize cache-line-size configuration register if needed.
+ */
+ if (pci_set_cache_line_size_register &&
+ ddi_getprop(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
+ "cache-line-size", 0) == 0) {
+
+ pci_config_put8(config_handle, PCI_CONF_CACHE_LINESZ,
+ PCI_CACHE_LINE_SIZE);
+ n = pci_config_get8(config_handle, PCI_CONF_CACHE_LINESZ);
+ if (n != 0)
+ (void) ndi_prop_update_int(DDI_DEV_T_NONE, child,
+ "cache-line-size", n);
+ }
+
+ /*
+ * Initialize latency timer registers if needed.
+ */
+ if (pci_set_latency_timer_register &&
+ ddi_getprop(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
+ "latency-timer", 0) == 0) {
+
+ latency_timer = pci_latency_timer;
+ if ((header_type & PCI_HEADER_TYPE_M) == PCI_HEADER_ONE) {
+ pci_config_put8(config_handle, PCI_BCNF_LATENCY_TIMER,
+ latency_timer);
+ } else {
+ min_gnt = pci_config_get8(config_handle,
+ PCI_CONF_MIN_G);
+ DEBUG2(DBG_INIT_CLD, pci_p->pci_dip, "%s: min_gnt=%x\n",
+ ddi_driver_name(child), min_gnt);
+ if (min_gnt != 0) {
+ switch (pci_p->pci_pbm_p->pbm_speed) {
+ case PBM_SPEED_33MHZ:
+ latency_timer = min_gnt * 8;
+ break;
+ case PBM_SPEED_66MHZ:
+ latency_timer = min_gnt * 4;
+ break;
+ }
+ }
+ }
+ latency_timer = MIN(latency_timer, 0xff);
+ pci_config_put8(config_handle, PCI_CONF_LATENCY_TIMER,
+ latency_timer);
+ n = pci_config_get8(config_handle, PCI_CONF_LATENCY_TIMER);
+ if (n != 0)
+ (void) ndi_prop_update_int(DDI_DEV_T_NONE, child,
+ "latency-timer", n);
+ }
+
+ pci_config_teardown(&config_handle);
+
+ /*
+ * Handle chip specific init-child tasks.
+ */
+ pci_post_init_child(pci_p, child);
+
+ return (DDI_SUCCESS);
+}
+
+/*
+ * get_nreg_set
+ *
+ * Given a dev info pointer to a pci child, this routine returns the
+ * number of sets in its "reg" property.
+ *
+ * used by: pci_ctlops() - DDI_CTLOPS_NREGS
+ *
+ * return value: # of reg sets on success, zero on error
+ */
+uint_t
+get_nreg_set(dev_info_t *child)
+{
+ pci_regspec_t *pci_rp;
+ int i, n;
+
+ /*
+ * Get the reg property for the device.
+ */
+ if (ddi_getlongprop(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "reg",
+ (caddr_t)&pci_rp, &i) != DDI_SUCCESS)
+ return (0);
+
+ n = i / (int)sizeof (pci_regspec_t);
+ kmem_free(pci_rp, i);
+ return (n);
+}
+
+
+/*
+ * get_nintr
+ *
+ * Given a dev info pointer to a pci child, this routine returns the
+ * number of items in its "interrupts" property.
+ *
+ * used by: pci_ctlops() - DDI_CTLOPS_NREGS
+ *
+ * return value: # of interrupts on success, zero on error
+ */
+uint_t
+get_nintr(dev_info_t *child)
+{
+ int *pci_ip;
+ int i, n;
+
+ if (ddi_getlongprop(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
+ "interrupts", (caddr_t)&pci_ip, &i) != DDI_SUCCESS)
+ return (0);
+
+ n = i / (int)sizeof (uint_t);
+ kmem_free(pci_ip, i);
+ return (n);
+}
+
+uint64_t
+pci_get_cfg_pabase(pci_t *pci_p)
+{
+ int i;
+ pci_ranges_t *rangep = pci_p->pci_ranges;
+ int nrange = pci_p->pci_ranges_length / sizeof (pci_ranges_t);
+ uint32_t cfg_space_type = PCI_REG_ADDR_G(PCI_ADDR_CONFIG);
+
+ ASSERT(cfg_space_type == 0);
+
+ for (i = 0; i < nrange; i++, rangep++) {
+ if (PCI_REG_ADDR_G(rangep->child_high) == cfg_space_type)
+ break;
+ }
+
+ if (i >= nrange)
+ cmn_err(CE_PANIC, "no cfg space in pci(%x) ranges prop.\n",
+ (void *)pci_p);
+
+ return (((uint64_t)rangep->parent_high << 32) | rangep->parent_low);
+}
+
+int
+pci_cfg_report(dev_info_t *dip, ddi_fm_error_t *derr, pci_errstate_t *pci_err_p,
+ int caller, uint32_t prierr)
+{
+ int fatal = 0;
+ int nonfatal = 0;
+ int i;
+ pci_target_err_t tgt_err;
+
+ ASSERT(dip);
+
+ derr->fme_ena = derr->fme_ena ? derr->fme_ena :
+ fm_ena_generate(0, FM_ENA_FMT1);
+
+ for (i = 0; pci_err_tbl[i].err_class != NULL; i++) {
+ if (pci_err_p->pci_cfg_stat & pci_err_tbl[i].reg_bit) {
+ char buf[FM_MAX_CLASS];
+
+ (void) snprintf(buf, FM_MAX_CLASS, "%s.%s",
+ PCI_ERROR_SUBCLASS,
+ pci_err_tbl[i].err_class);
+ ddi_fm_ereport_post(dip, buf, derr->fme_ena,
+ DDI_NOSLEEP, FM_VERSION, DATA_TYPE_UINT8, 0,
+ PCI_CONFIG_STATUS, DATA_TYPE_UINT16,
+ pci_err_p->pci_cfg_stat,
+ PCI_CONFIG_COMMAND, DATA_TYPE_UINT16,
+ pci_err_p->pci_cfg_comm,
+ PCI_PA, DATA_TYPE_UINT64,
+ pci_err_p->pci_pa,
+ NULL);
+
+ switch (pci_err_tbl[i].reg_bit) {
+ case PCI_STAT_S_SYSERR:
+ /*
+ * address parity error on dma - treat as fatal
+ */
+ fatal++;
+ break;
+ case PCI_STAT_R_MAST_AB:
+ case PCI_STAT_R_TARG_AB:
+ case PCI_STAT_S_PERROR:
+ if (prierr) {
+ /*
+ * piow case are already handled in
+ * pbm_afsr_report()
+ */
+ break;
+ }
+ if (caller != PCI_TRAP_CALL) {
+ /*
+ * if we haven't come from trap handler
+ * we won't have an address
+ */
+ fatal++;
+ break;
+ }
+
+ /*
+ * queue target ereport - use return from
+ * pci_lookup_handle() to determine if sync
+ * or async
+ */
+ tgt_err.tgt_err_ena = derr->fme_ena;
+ tgt_err.tgt_err_class =
+ pci_err_tbl[i].terr_class;
+ tgt_err.tgt_bridge_type = PCI_ERROR_SUBCLASS;
+ tgt_err.tgt_err_addr =
+ (uint64_t)derr->fme_bus_specific;
+ nonfatal++;
+ errorq_dispatch(pci_target_queue,
+ (void *)&tgt_err,
+ sizeof (pci_target_err_t),
+ ERRORQ_ASYNC);
+ break;
+ default:
+ /*
+ * dpe on dma write or ta on dma
+ */
+ nonfatal++;
+ break;
+ }
+ }
+ }
+
+ if (fatal)
+ return (DDI_FM_FATAL);
+ else if (nonfatal)
+ return (DDI_FM_NONFATAL);
+
+ return (DDI_FM_OK);
+}
+
+void
+pci_child_cfg_save(dev_info_t *dip)
+{
+ dev_info_t *cdip;
+ int ret = DDI_SUCCESS;
+
+ /*
+ * Save the state of the configuration headers of child
+ * nodes.
+ */
+
+ for (cdip = ddi_get_child(dip); cdip != NULL;
+ cdip = ddi_get_next_sibling(cdip)) {
+
+ /*
+ * Not interested in children who are not already
+ * init'ed. They will be set up in init_child().
+ */
+ if (i_ddi_node_state(cdip) < DS_INITIALIZED) {
+ DEBUG2(DBG_DETACH, dip, "DDI_SUSPEND: skipping "
+ "%s%d not in CF1\n", ddi_driver_name(cdip),
+ ddi_get_instance(cdip));
+
+ continue;
+ }
+
+ /*
+ * Only save config registers if not already saved by child.
+ */
+ if (ddi_prop_exists(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
+ SAVED_CONFIG_REGS) == 1) {
+
+ continue;
+ }
+
+ /*
+ * The nexus needs to save config registers. Create a property
+ * so it knows to restore on resume.
+ */
+ ret = ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip,
+ "nexus-saved-config-regs");
+
+ if (ret != DDI_PROP_SUCCESS) {
+ cmn_err(CE_WARN, "%s%d can't update prop %s",
+ ddi_driver_name(cdip), ddi_get_instance(cdip),
+ "nexus-saved-config-regs");
+ }
+
+ (void) pci_save_config_regs(cdip);
+ }
+}
+
+void
+pci_child_cfg_restore(dev_info_t *dip)
+{
+ dev_info_t *cdip;
+
+ /*
+ * Restore config registers for children that did not save
+ * their own registers. Children pwr states are UNKNOWN after
+ * a resume since it is possible for the PM framework to call
+ * resume without an actual power cycle. (ie if suspend fails).
+ */
+ for (cdip = ddi_get_child(dip); cdip != NULL;
+ cdip = ddi_get_next_sibling(cdip)) {
+
+ /*
+ * Not interested in children who are not already
+ * init'ed. They will be set up by init_child().
+ */
+ if (i_ddi_node_state(cdip) < DS_INITIALIZED) {
+ DEBUG2(DBG_DETACH, dip,
+ "DDI_RESUME: skipping %s%d not in CF1\n",
+ ddi_driver_name(cdip), ddi_get_instance(cdip));
+ continue;
+ }
+
+ /*
+ * Only restore config registers if saved by nexus.
+ */
+ if (ddi_prop_exists(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
+ "nexus-saved-config-regs") == 1) {
+ (void) pci_restore_config_regs(cdip);
+
+ DEBUG2(DBG_PWR, dip,
+ "DDI_RESUME: nexus restoring %s%d config regs\n",
+ ddi_driver_name(cdip), ddi_get_instance(cdip));
+
+ if (ndi_prop_remove(DDI_DEV_T_NONE, cdip,
+ "nexus-saved-config-regs") != DDI_PROP_SUCCESS) {
+ cmn_err(CE_WARN, "%s%d can't remove prop %s",
+ ddi_driver_name(cdip),
+ ddi_get_instance(cdip),
+ "nexus-saved-config-regs");
+ }
+ }
+ }
+}