diff options
Diffstat (limited to 'usr/src/uts/common')
37 files changed, 10155 insertions, 5480 deletions
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files index 8ded90867e..3aa180f583 100644 --- a/usr/src/uts/common/Makefile.files +++ b/usr/src/uts/common/Makefile.files @@ -132,6 +132,8 @@ GENUNIX_OBJS += \ cyclic.o \ ddi.o \ ddifm.o \ + ddi_hp_impl.o \ + ddi_hp_ndi.o \ ddi_intr.o \ ddi_intr_impl.o \ ddi_intr_irm.o \ @@ -978,11 +980,9 @@ VUIDPS2_OBJS += vuidmice.o vuidps2.o HPCSVC_OBJS += hpcsvc.o -PCIHPNEXUS_OBJS += pcihp.o - -PCIEHPCNEXUS_OBJS += pciehpc.o +PCIE_MISC_OBJS += pcie.o pcie_fault.o pcie_hp.o pciehpc.o pcishpc.o pcie_pwr.o -PCISHPC_OBJS += pcishpc.o +PCIHPNEXUS_OBJS += pcihp.o OPENEEPR_OBJS += openprom.o @@ -1813,8 +1813,6 @@ NATIVE_INC_PATH += $(INC_PATH) $(CCYFLAG)$(UTSBASE)/common AS_INC_PATH += $(INC_PATH) -I$(UTSBASE)/common INCLUDE_PATH += $(INC_PATH) $(CCYFLAG)$(UTSBASE)/common -# -PCIE_MISC_OBJS += pcie.o pcie_fault.o pcie_pwr.o PCIEB_OBJS += pcieb.o # Chelsio N110 10G NIC driver module diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules index e45169a005..ebb286f385 100644 --- a/usr/src/uts/common/Makefile.rules +++ b/usr/src/uts/common/Makefile.rules @@ -716,11 +716,7 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/hotplug/hpcsvc/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) -$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/hotplug/pciehpc/%.c - $(COMPILE.c) -o $@ $< - $(CTFCONVERT_O) - -$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/hotplug/pcishpc/%.c +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/pciex/hotplug/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -1993,10 +1989,7 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/pciex/%.c $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/hotplug/hpcsvc/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) -$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/hotplug/pciehpc/%.c - @($(LHEAD) $(LINT.c) $< $(LTAIL)) - -$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/hotplug/pcishpc/%.c +$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/pciex/hotplug/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/hotplug/pcihp/%.c diff --git a/usr/src/uts/common/io/busra.c b/usr/src/uts/common/io/busra.c index 4a059560f7..8a72954676 100644 --- a/usr/src/uts/common/io/busra.c +++ b/usr/src/uts/common/io/busra.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -127,6 +127,15 @@ static int isnot_pow2(uint64_t value); static int claim_pci_busnum(dev_info_t *dip, void *arg); static int ra_map_exist(dev_info_t *dip, char *type); +static int pci_get_available_prop(dev_info_t *dip, uint64_t base, + uint64_t len, char *busra_type); +static int pci_put_available_prop(dev_info_t *dip, uint64_t base, + uint64_t len, char *busra_type); +static uint32_t pci_type_ra2pci(char *type); +static boolean_t is_pcie_fabric(dev_info_t *dip); + +#define PCI_ADDR_TYPE_MASK (PCI_REG_ADDR_M | PCI_REG_PF_M) +#define PCI_ADDR_TYPE_INVAL 0xffffffff #define RA_INSERT(prev, el) \ el->ra_next = *prev; \ @@ -151,7 +160,7 @@ _init() int ret; mutex_init(&ra_lock, NULL, MUTEX_DRIVER, - (void *)(intptr_t)__ipltospl(SPL7 - 1)); + (void *)(intptr_t)__ipltospl(SPL7 - 1)); if ((ret = mod_install(&modlinkage)) != 0) { mutex_destroy(&ra_lock); } @@ -206,9 +215,9 @@ ndi_ra_map_setup(dev_info_t *dip, char *type) if (dipmap == NULL) { if (backtype == NULL) { typemapp = (struct ra_type_map *) - kmem_zalloc(sizeof (*typemapp), KM_SLEEP); + kmem_zalloc(sizeof (*typemapp), KM_SLEEP); typemapp->type = (char *)kmem_zalloc(strlen(type) + 1, - KM_SLEEP); + KM_SLEEP); (void) strcpy(typemapp->type, type); RA_INSERT(&ra_map_list_head, typemapp); } else { @@ -217,7 +226,7 @@ ndi_ra_map_setup(dev_info_t *dip, char *type) if (backdip == NULL) { /* allocate and insert in list of dips for this type */ dipmap = (struct ra_dip_type *) - kmem_zalloc(sizeof (*dipmap), KM_SLEEP); + kmem_zalloc(sizeof (*dipmap), KM_SLEEP); dipmap->ra_dip = dip; RA_INSERT(&typemapp->ra_dip_list, dipmap); } @@ -413,7 +422,7 @@ ndi_ra_free(dev_info_t *dip, uint64_t base, uint64_t len, char *type, } else if (base < mapp->ra_base) { /* somewhere in between so just an insert */ newmap = (struct ra_resource *) - kmem_zalloc(sizeof (*newmap), KM_SLEEP); + kmem_zalloc(sizeof (*newmap), KM_SLEEP); newmap->ra_base = base; newmap->ra_len = len; RA_INSERT(backp, newmap); @@ -423,13 +432,20 @@ ndi_ra_free(dev_info_t *dip, uint64_t base, uint64_t len, char *type, if (mapp == NULL) { /* stick on end */ newmap = (struct ra_resource *) - kmem_zalloc(sizeof (*newmap), KM_SLEEP); + kmem_zalloc(sizeof (*newmap), KM_SLEEP); newmap->ra_base = base; newmap->ra_len = len; RA_INSERT(backp, newmap); } mutex_exit(&ra_lock); + + /* + * Update dip's "available" property, adding this piece of + * resource to the pool. + */ + (void) pci_put_available_prop(dip, base, len, type); +done: return (NDI_SUCCESS); overlap: @@ -485,10 +501,9 @@ adjust_link(struct ra_resource **backp, struct ra_resource *mapp, } else { /* in the middle */ newmap = (struct ra_resource *) - kmem_zalloc(sizeof (*newmap), KM_SLEEP); + kmem_zalloc(sizeof (*newmap), KM_SLEEP); newmap->ra_base = base + len; - newmap->ra_len = mapp->ra_len - - (len + newlen); + newmap->ra_len = mapp->ra_len - (len + newlen); mapp->ra_len = newlen; RA_INSERT(&(mapp->ra_next), newmap); } @@ -523,7 +538,7 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, if (req->ra_flags & NDI_RA_ALIGN_SIZE) { if (isnot_pow2(req->ra_len)) { DEBUGPRT(CE_WARN, "ndi_ra_alloc: bad length(pow2) 0x%" - PRIx64, req->ra_len); + PRIx64, req->ra_len); *retbasep = 0; *retlenp = 0; return (NDI_FAILURE); @@ -543,7 +558,7 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, } DEBUGPRT(CE_CONT, "ndi_ra_alloc: mapp = %p len=%" PRIx64 ", mask=%" - PRIx64 "\n", (void *)mapp, len, mask); + PRIx64 "\n", (void *)mapp, len, mask); backp = &(dipmap->ra_rangeset); backlargestp = NULL; @@ -560,11 +575,10 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, if ((upper == 0) || (upper < req->ra_boundlen)) upper = ~(uint64_t)0; DEBUGPRT(CE_CONT, "ndi_ra_alloc: ra_len = %" PRIx64 ", len = %" - PRIx64 " ra_base=%" PRIx64 ", mask=%" PRIx64 - "\n", mapp->ra_len, len, mapp->ra_base, mask); - for (; mapp != NULL && - (mapp->ra_base + mapp->ra_len) < lower; - backp = &(mapp->ra_next), mapp = mapp->ra_next) { + PRIx64 " ra_base=%" PRIx64 ", mask=%" PRIx64 + "\n", mapp->ra_len, len, mapp->ra_base, mask); + for (; mapp != NULL && (mapp->ra_base + mapp->ra_len) < lower; + backp = &(mapp->ra_next), mapp = mapp->ra_next) { if (((mapp->ra_len + mapp->ra_base) == 0) || ((mapp->ra_len + mapp->ra_base) < mapp->ra_len)) /* @@ -583,9 +597,9 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, if (!(req->ra_flags & NDI_RA_ALLOC_SPECIFIED)) { /* first fit - not user specified */ DEBUGPRT(CE_CONT, "ndi_ra_alloc(unspecified request)" - "lower=%" PRIx64 ", upper=%" PRIx64 "\n", lower, upper); + "lower=%" PRIx64 ", upper=%" PRIx64 "\n", lower, upper); for (; mapp != NULL && mapp->ra_base <= upper; - backp = &(mapp->ra_next), mapp = mapp->ra_next) { + backp = &(mapp->ra_next), mapp = mapp->ra_next) { DEBUGPRT(CE_CONT, "ndi_ra_alloc: ra_len = %" PRIx64 ", len = %" PRIx64 "", mapp->ra_len, len); @@ -606,7 +620,7 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, base = base & ~mask; base += (mask + 1); DEBUGPRT(CE_CONT, "\tnew base=%" PRIx64 "\n", - base); + base); /* * Check to see if the new base is past @@ -622,7 +636,7 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, remlen = upper - base; else remlen = mapp->ra_len - - (base - mapp->ra_base); + (base - mapp->ra_base); if ((backlargestp == NULL) || (largestlen < remlen)) { @@ -656,7 +670,7 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, base = req->ra_addr; len = req->ra_len; for (; mapp != NULL && mapp->ra_base <= upper; - backp = &(mapp->ra_next), mapp = mapp->ra_next) { + backp = &(mapp->ra_next), mapp = mapp->ra_next) { if (base >= mapp->ra_base && ((base - mapp->ra_base) < mapp->ra_len)) { /* @@ -696,7 +710,7 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, (req->ra_flags & NDI_RA_ALLOC_PARTIAL_OK) && (backlargestp != NULL)) { adjust_link(backlargestp, *backlargestp, largestbase, - largestlen); + largestlen); base = largestbase; len = largestlen; @@ -712,6 +726,14 @@ ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep, *retbasep = base; *retlenp = len; } + + /* + * Update dip's "available" property, substract this piece of + * resource from the pool. + */ + if ((rval == NDI_SUCCESS) || (rval == NDI_RA_PARTIAL_REQ)) + (void) pci_get_available_prop(dip, *retbasep, *retlenp, type); + return (rval); } @@ -748,7 +770,7 @@ isa_resource_setup() used = ddi_find_devinfo("used-resources", -1, 0); if (used == NULL) { DEBUGPRT(CE_CONT, - "isa_resource_setup: used-resources not found"); + "isa_resource_setup: used-resources not found"); return (NDI_FAILURE); } @@ -869,7 +891,7 @@ ra_dump_all(char *type, dev_info_t *dip) } cmn_err(CE_CONT, "type is %s\n", typemap->type); for (dipmap = typemap->ra_dip_list; dipmap != NULL; - dipmap = dipmap->ra_next) { + dipmap = dipmap->ra_next) { if (dip != NULL) { if ((dipmap->ra_dip) != dip) continue; @@ -877,7 +899,7 @@ ra_dump_all(char *type, dev_info_t *dip) cmn_err(CE_CONT, " dip is %p\n", (void *)dipmap->ra_dip); for (res = dipmap->ra_rangeset; res != NULL; - res = res->ra_next) { + res = res->ra_next) { cmn_err(CE_CONT, "\t range is %" PRIx64 " %" PRIx64 "\n", res->ra_base, res->ra_len); @@ -942,6 +964,26 @@ pci_resource_setup(dev_info_t *dip) * code would really become an assert to make sure this * function is not called for the same dip twice. */ + /* + * Another user for the check below is hotplug PCI/PCIe bridges. + * + * For PCI/PCIE devices under a PCIE hierarchy, ndi_ra_alloc/free + * will update the devinfo node's "available" property, to reflect + * the fact that a piece of resource has been removed/added to + * a devinfo node. + * During probe of a new PCI bridge in the hotplug case, PCI + * configurator firstly allocates maximum MEM/IO from its parent, + * then calls ndi_ra_free() to use these resources to setup busra + * pool for the new bridge, as well as adding these resources to + * the "available" property of the new devinfo node. Then configu- + * rator will attach driver for the bridge before probing its + * children, and the bridge driver will then initialize its hotplug + * contollers (if it supports hotplug) and HPC driver will call + * this function to setup the busra pool, but the resource pool + * has already been setup at the first of pcicfg_probe_bridge(), + * thus we need the check below to return directly in this case. + * Otherwise the ndi_ra_free() below will see overlapping resources. + */ { if (ra_map_exist(dip, NDI_RA_TYPE_MEM) == NDI_SUCCESS) { return (NDI_FAILURE); @@ -979,45 +1021,54 @@ pci_resource_setup(dev_info_t *dip) if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "available", (caddr_t)®s, &rlen) == DDI_SUCCESS) { /* + * Remove "available" property as the entries will be + * re-created in ndi_ra_free() below, note prom based + * property will not be removed. But in ndi_ra_free() + * we'll be creating non prom based property entries. + */ + (void) ndi_prop_remove(DDI_DEV_T_NONE, dip, "available"); + /* * create the available resource list for both memory and * io space */ rcount = rlen / sizeof (pci_regspec_t); for (i = 0; i < rcount; i++) { - switch (PCI_REG_ADDR_G(regs[i].pci_phys_hi)) { - case PCI_REG_ADDR_G(PCI_ADDR_MEM32): - (void) ndi_ra_free(dip, - (uint64_t)regs[i].pci_phys_low, - (uint64_t)regs[i].pci_size_low, - (regs[i].pci_phys_hi & PCI_REG_PF_M) ? - NDI_RA_TYPE_PCI_PREFETCH_MEM : NDI_RA_TYPE_MEM, - 0); - break; - case PCI_REG_ADDR_G(PCI_ADDR_MEM64): - (void) ndi_ra_free(dip, - ((uint64_t)(regs[i].pci_phys_mid) << 32) | - ((uint64_t)(regs[i].pci_phys_low)), - ((uint64_t)(regs[i].pci_size_hi) << 32) | - ((uint64_t)(regs[i].pci_size_low)), - (regs[i].pci_phys_hi & PCI_REG_PF_M) ? - NDI_RA_TYPE_PCI_PREFETCH_MEM : NDI_RA_TYPE_MEM, - 0); - break; - case PCI_REG_ADDR_G(PCI_ADDR_IO): - (void) ndi_ra_free(dip, - (uint64_t)regs[i].pci_phys_low, - (uint64_t)regs[i].pci_size_low, - NDI_RA_TYPE_IO, - 0); - break; - case PCI_REG_ADDR_G(PCI_ADDR_CONFIG): - break; - default: - cmn_err(CE_WARN, - "pci_resource_setup: bad addr type: %x\n", - PCI_REG_ADDR_G(regs[i].pci_phys_hi)); - break; - } + switch (PCI_REG_ADDR_G(regs[i].pci_phys_hi)) { + case PCI_REG_ADDR_G(PCI_ADDR_MEM32): + (void) ndi_ra_free(dip, + (uint64_t)regs[i].pci_phys_low, + (uint64_t)regs[i].pci_size_low, + (regs[i].pci_phys_hi & PCI_REG_PF_M) ? + NDI_RA_TYPE_PCI_PREFETCH_MEM : + NDI_RA_TYPE_MEM, + 0); + break; + case PCI_REG_ADDR_G(PCI_ADDR_MEM64): + (void) ndi_ra_free(dip, + ((uint64_t)(regs[i].pci_phys_mid) << 32) | + ((uint64_t)(regs[i].pci_phys_low)), + ((uint64_t)(regs[i].pci_size_hi) << 32) | + ((uint64_t)(regs[i].pci_size_low)), + (regs[i].pci_phys_hi & PCI_REG_PF_M) ? + NDI_RA_TYPE_PCI_PREFETCH_MEM : + NDI_RA_TYPE_MEM, + 0); + break; + case PCI_REG_ADDR_G(PCI_ADDR_IO): + (void) ndi_ra_free(dip, + (uint64_t)regs[i].pci_phys_low, + (uint64_t)regs[i].pci_size_low, + NDI_RA_TYPE_IO, + 0); + break; + case PCI_REG_ADDR_G(PCI_ADDR_CONFIG): + break; + default: + cmn_err(CE_WARN, + "pci_resource_setup: bad addr type: %x\n", + PCI_REG_ADDR_G(regs[i].pci_phys_hi)); + break; + } } kmem_free(regs, rlen); } @@ -1174,22 +1225,16 @@ pci_resource_setup_avail(dev_info_t *dip, pci_regspec_t *avail_p, int entries) switch (PCI_REG_ADDR_G(avail_p->pci_phys_hi)) { case PCI_REG_ADDR_G(PCI_ADDR_MEM32): { - (void) ndi_ra_free(dip, - (uint64_t)avail_p->pci_phys_low, - (uint64_t)avail_p->pci_size_low, - (avail_p->pci_phys_hi & - PCI_REG_PF_M) ? - NDI_RA_TYPE_PCI_PREFETCH_MEM : - NDI_RA_TYPE_MEM, - 0); + (void) ndi_ra_free(dip, (uint64_t)avail_p->pci_phys_low, + (uint64_t)avail_p->pci_size_low, + (avail_p->pci_phys_hi & PCI_REG_PF_M) ? + NDI_RA_TYPE_PCI_PREFETCH_MEM : NDI_RA_TYPE_MEM, + 0); } break; case PCI_REG_ADDR_G(PCI_ADDR_IO): - (void) ndi_ra_free(dip, - (uint64_t)avail_p->pci_phys_low, - (uint64_t)avail_p->pci_size_low, - NDI_RA_TYPE_IO, - 0); + (void) ndi_ra_free(dip, (uint64_t)avail_p->pci_phys_low, + (uint64_t)avail_p->pci_size_low, NDI_RA_TYPE_IO, 0); break; default: goto err; @@ -1204,6 +1249,457 @@ pci_resource_setup_avail(dev_info_t *dip, pci_regspec_t *avail_p, int entries) err: cmn_err(CE_WARN, "pci_resource_setup_avail: bad entry[%d]=%x\n", - i, avail_p->pci_phys_hi); + i, avail_p->pci_phys_hi); return (NDI_FAILURE); } + +/* + * Return true if the devinfo node resides on PCI or PCI Express bus, + * sitting in a PCI Express hierarchy. + */ +static boolean_t +is_pcie_fabric(dev_info_t *dip) +{ + dev_info_t *root = ddi_root_node(); + dev_info_t *pdip; + boolean_t found = B_FALSE; + char *bus; + + /* + * Is this pci/pcie ? + */ + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS, "device_type", &bus) != + DDI_PROP_SUCCESS) { + DEBUGPRT(CE_WARN, "is_pcie_fabric: cannot find " + "\"device_type\" property for dip %p\n", (void *)dip); + return (B_FALSE); + } + + if (strcmp(bus, "pciex") == 0) { + /* pcie bus, done */ + ddi_prop_free(bus); + return (B_TRUE); + } else if (strcmp(bus, "pci") == 0) { + /* + * pci bus, fall through to check if it resides in + * a pcie hierarchy. + */ + ddi_prop_free(bus); + } else { + /* other bus, return failure */ + ddi_prop_free(bus); + return (B_FALSE); + } + + /* + * Does this device reside in a pcie fabric ? + */ + for (pdip = ddi_get_parent(dip); pdip && (pdip != root) && + !found; pdip = ddi_get_parent(pdip)) { + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, pdip, + DDI_PROP_DONTPASS, "device_type", &bus) != + DDI_PROP_SUCCESS) + break; + + if (strcmp(bus, "pciex") == 0) + found = B_TRUE; + + ddi_prop_free(bus); + } + + return (found); +} + +/* + * Remove a piece of IO/MEM resource from "available" property of 'dip'. + */ +static int +pci_get_available_prop(dev_info_t *dip, uint64_t base, uint64_t len, + char *busra_type) +{ + pci_regspec_t *regs, *newregs; + uint_t status; + int rlen, rcount; + int i, j, k; + uint64_t dlen; + boolean_t found = B_FALSE; + uint32_t type; + + /* check if we're manipulating MEM/IO resource */ + if ((type = pci_type_ra2pci(busra_type)) == PCI_ADDR_TYPE_INVAL) + return (DDI_SUCCESS); + + /* check if dip is a pci/pcie device resides in a pcie fabric */ + if (!is_pcie_fabric(dip)) + return (DDI_SUCCESS); + + status = ddi_getlongprop(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, + "available", (caddr_t)®s, &rlen); + + ASSERT(status == DDI_SUCCESS); + if (status != DDI_SUCCESS) + return (status); + + /* + * The updated "available" property will at most have one more entry + * than existing one (when the requested range is in the middle of + * the matched property entry) + */ + newregs = kmem_alloc(rlen + sizeof (pci_regspec_t), KM_SLEEP); + + rcount = rlen / sizeof (pci_regspec_t); + for (i = 0, j = 0; i < rcount; i++) { + if (type == (regs[i].pci_phys_hi & PCI_ADDR_TYPE_MASK)) { + uint64_t range_base, range_len; + + range_base = ((uint64_t)(regs[i].pci_phys_mid) << 32) | + ((uint64_t)(regs[i].pci_phys_low)); + range_len = ((uint64_t)(regs[i].pci_size_hi) << 32) | + ((uint64_t)(regs[i].pci_size_low)); + + if ((base < range_base) || + (base + len > range_base + range_len)) { + /* + * not a match, copy the entry + */ + goto copy_entry; + } + + /* + * range_base base base+len range_base + * +range_len + * +------------+-----------+----------+ + * | |///////////| | + * +------------+-----------+----------+ + */ + /* + * Found a match, remove the range out of this entry. + */ + found = B_TRUE; + + dlen = base - range_base; + if (dlen != 0) { + newregs[j].pci_phys_hi = regs[i].pci_phys_hi; + newregs[j].pci_phys_mid = + (uint32_t)(range_base >> 32); + newregs[j].pci_phys_low = + (uint32_t)(range_base); + newregs[j].pci_size_hi = (uint32_t)(dlen >> 32); + newregs[j].pci_size_low = (uint32_t)dlen; + j++; + } + + dlen = (range_base + range_len) - (base + len); + if (dlen != 0) { + newregs[j].pci_phys_hi = regs[i].pci_phys_hi; + newregs[j].pci_phys_mid = + (uint32_t)((base + len)>> 32); + newregs[j].pci_phys_low = + (uint32_t)(base + len); + newregs[j].pci_size_hi = (uint32_t)(dlen >> 32); + newregs[j].pci_size_low = (uint32_t)dlen; + j++; + } + + /* + * We've allocated the resource from the matched + * entry, almost finished but still need to copy + * the rest entries from the original property + * array. + */ + for (k = i + 1; k < rcount; k++) { + newregs[j] = regs[k]; + j++; + } + + goto done; + + } else { +copy_entry: + newregs[j] = regs[i]; + j++; + } + } + +done: + /* + * This should not fail so assert it. For non-debug kernel we don't + * want to panic thus only logging a warning message. + */ + ASSERT(found == B_TRUE); + if (!found) { + cmn_err(CE_WARN, "pci_get_available_prop: failed to remove " + "resource from dip %p : base 0x%" PRIx64 ", len 0x%" PRIX64 + ", type 0x%x\n", (void *)dip, base, len, type); + kmem_free(newregs, rlen + sizeof (pci_regspec_t)); + kmem_free(regs, rlen); + + return (DDI_FAILURE); + } + + /* + * Found the resources from parent, update the "available" + * property. + */ + if (j == 0) { + /* all the resources are consumed, remove the property */ + (void) ndi_prop_remove(DDI_DEV_T_NONE, dip, "available"); + } else { + /* + * There are still resource available in the parent dip, + * update with the remaining resources. + */ + (void) ndi_prop_update_int_array(DDI_DEV_T_NONE, dip, + "available", (int *)newregs, + (j * sizeof (pci_regspec_t)) / sizeof (int)); + } + + kmem_free(newregs, rlen + sizeof (pci_regspec_t)); + kmem_free(regs, rlen); + + return (DDI_SUCCESS); +} + +/* + * Add a piece of IO/MEM resource to "available" property of 'dip'. + */ +static int +pci_put_available_prop(dev_info_t *dip, uint64_t base, uint64_t len, + char *busra_type) +{ + pci_regspec_t *regs, *newregs; + uint_t status; + int rlen, rcount; + int i, j, k; + int matched = 0; + uint64_t orig_base = base; + uint64_t orig_len = len; + uint32_t type; + + /* check if we're manipulating MEM/IO resource */ + if ((type = pci_type_ra2pci(busra_type)) == PCI_ADDR_TYPE_INVAL) + return (DDI_SUCCESS); + + /* check if dip is a pci/pcie device resides in a pcie fabric */ + if (!is_pcie_fabric(dip)) + return (DDI_SUCCESS); + + status = ddi_getlongprop(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, + "available", (caddr_t)®s, &rlen); + + switch (status) { + case DDI_PROP_NOT_FOUND: + goto not_found; + + case DDI_PROP_SUCCESS: + break; + + default: + return (status); + } + + /* + * The "available" property exist on the node, try to put this + * resource back, merge if there are adjacent resources. + * + * The updated "available" property will at most have one more entry + * than existing one (when there is no adjacent entries thus the new + * resource is appended at the end) + */ + newregs = kmem_alloc(rlen + sizeof (pci_regspec_t), KM_SLEEP); + + rcount = rlen / sizeof (pci_regspec_t); + for (i = 0, j = 0; i < rcount; i++) { + if (type == (regs[i].pci_phys_hi & PCI_ADDR_TYPE_MASK)) { + uint64_t range_base, range_len; + + range_base = ((uint64_t)(regs[i].pci_phys_mid) << 32) | + ((uint64_t)(regs[i].pci_phys_low)); + range_len = ((uint64_t)(regs[i].pci_size_hi) << 32) | + ((uint64_t)(regs[i].pci_size_low)); + + if ((base + len < range_base) || + (base > range_base + range_len)) { + /* + * Not adjacent, copy the entry and contiue + */ + goto copy_entry; + } + + /* + * Adjacent or overlap? + * + * Should not have overlapping resources so assert it. + * For non-debug kernel we don't want to panic thus + * only logging a warning message. + */ +#if 0 + ASSERT((base + len == range_base) || + (base == range_base + range_len)); +#endif + if ((base + len != range_base) && + (base != range_base + range_len)) { + cmn_err(CE_WARN, "pci_put_available_prop: " + "failed to add resource to dip %p : " + "base 0x%" PRIx64 ", len 0x%" PRIx64 " " + "overlaps with existing resource " + "base 0x%" PRIx64 ", len 0x%" PRIx64 "\n", + (void *)dip, orig_base, orig_len, + range_base, range_len); + + goto failure; + } + + /* + * On the left: + * + * base range_base + * +-------------+-------------+ + * |/////////////| | + * +-------------+-------------+ + * len range_len + * + * On the right: + * + * range_base base + * +-------------+-------------+ + * | |/////////////| + * +-------------+-------------+ + * range_len len + */ + /* + * There are at most two piece of resources adjacent + * with this resource, assert it. + */ + ASSERT(matched < 2); + + if (!(matched < 2)) { + cmn_err(CE_WARN, "pci_put_available_prop: " + "failed to add resource to dip %p : " + "base 0x%" PRIx64 ", len 0x%" PRIx64 " " + "found overlaps in existing resources\n", + (void *)dip, orig_base, orig_len); + + goto failure; + } + + /* setup base & len to refer to the merged range */ + len += range_len; + if (base == range_base + range_len) + base = range_base; + + if (matched == 0) { + /* + * One adjacent entry, add this resource in + */ + newregs[j].pci_phys_hi = regs[i].pci_phys_hi; + newregs[j].pci_phys_mid = + (uint32_t)(base >> 32); + newregs[j].pci_phys_low = (uint32_t)(base); + newregs[j].pci_size_hi = (uint32_t)(len >> 32); + newregs[j].pci_size_low = (uint32_t)len; + + matched = 1; + k = j; + j++; + } else { /* matched == 1 */ + /* + * Two adjacent entries, merge them together + */ + newregs[k].pci_phys_hi = regs[i].pci_phys_hi; + newregs[k].pci_phys_mid = + (uint32_t)(base >> 32); + newregs[k].pci_phys_low = (uint32_t)(base); + newregs[k].pci_size_hi = (uint32_t)(len >> 32); + newregs[k].pci_size_low = (uint32_t)len; + + matched = 2; + } + } else { +copy_entry: + newregs[j] = regs[i]; + j++; + } + } + + if (matched == 0) { + /* No adjacent entries, append at end */ + ASSERT(j == rcount); + + /* + * According to page 15 of 1275 spec, bit "n" of "available" + * should be set to 1. + */ + newregs[j].pci_phys_hi = type; + newregs[j].pci_phys_hi |= PCI_REG_REL_M; + + newregs[j].pci_phys_mid = (uint32_t)(base >> 32); + newregs[j].pci_phys_low = (uint32_t)base; + newregs[j].pci_size_hi = (uint32_t)(len >> 32); + newregs[j].pci_size_low = (uint32_t)len; + + j++; + } + + (void) ndi_prop_update_int_array(DDI_DEV_T_NONE, dip, + "available", (int *)newregs, + (j * sizeof (pci_regspec_t)) / sizeof (int)); + + kmem_free(newregs, rlen + sizeof (pci_regspec_t)); + kmem_free(regs, rlen); + return (DDI_SUCCESS); + +not_found: + /* + * There is no "available" property on the parent node, create it. + */ + newregs = kmem_alloc(sizeof (pci_regspec_t), KM_SLEEP); + + /* + * According to page 15 of 1275 spec, bit "n" of "available" should + * be set to 1. + */ + newregs[0].pci_phys_hi = type; + newregs[0].pci_phys_hi |= PCI_REG_REL_M; + + newregs[0].pci_phys_mid = (uint32_t)(base >> 32); + newregs[0].pci_phys_low = (uint32_t)base; + newregs[0].pci_size_hi = (uint32_t)(len >> 32); + newregs[0].pci_size_low = (uint32_t)len; + + (void) ndi_prop_update_int_array(DDI_DEV_T_NONE, dip, + "available", (int *)newregs, + sizeof (pci_regspec_t) / sizeof (int)); + kmem_free(newregs, sizeof (pci_regspec_t)); + return (DDI_SUCCESS); + +failure: + kmem_free(newregs, rlen + sizeof (pci_regspec_t)); + kmem_free(regs, rlen); + return (DDI_FAILURE); +} + +static uint32_t +pci_type_ra2pci(char *type) +{ + uint32_t pci_type = PCI_ADDR_TYPE_INVAL; + + /* + * No 64 bit mem support for now + */ + if (strcmp(type, NDI_RA_TYPE_IO) == 0) { + pci_type = PCI_ADDR_IO; + + } else if (strcmp(type, NDI_RA_TYPE_MEM) == 0) { + pci_type = PCI_ADDR_MEM32; + + } else if (strcmp(type, NDI_RA_TYPE_PCI_PREFETCH_MEM) == 0) { + pci_type = PCI_ADDR_MEM32; + pci_type |= PCI_REG_PF_M; + } + + return (pci_type); +} diff --git a/usr/src/uts/common/io/devinfo.c b/usr/src/uts/common/io/devinfo.c index d1d02ce378..490d7fc28e 100644 --- a/usr/src/uts/common/io/devinfo.c +++ b/usr/src/uts/common/io/devinfo.c @@ -54,6 +54,10 @@ #include <sys/disp.h> #include <sys/kobj.h> #include <sys/crc32.h> +#include <sys/ddi_hp.h> +#include <sys/ddi_hp_impl.h> +#include <sys/sysmacros.h> +#include <sys/list.h> #ifdef DEBUG @@ -231,6 +235,12 @@ typedef struct i_lnode { i_link_t *link_out; } i_lnode_t; +typedef struct i_hp { + di_off_t hp_off; /* Offset of di_hp_t in snapshot */ + dev_info_t *hp_child; /* Child devinfo node of the di_hp_t */ + list_node_t hp_link; /* List linkage */ +} i_hp_t; + /* * Soft state associated with each instance of driver open. */ @@ -246,6 +256,8 @@ static struct di_state { mod_hash_t *lnode_hash; mod_hash_t *link_hash; + + list_t hp_list; } **di_states; static kmutex_t di_lock; /* serialize instance assignment */ @@ -298,6 +310,8 @@ static di_off_t di_getmdata(struct ddi_minor_data *, di_off_t *, di_off_t, struct di_state *); static di_off_t di_getppdata(struct dev_info *, di_off_t *, struct di_state *); static di_off_t di_getdpdata(struct dev_info *, di_off_t *, struct di_state *); +static di_off_t di_gethpdata(ddi_hp_cn_handle_t *, di_off_t *, + struct di_state *); static di_off_t di_getprop(int, struct ddi_prop **, di_off_t *, struct di_state *, struct dev_info *); static void di_allocmem(struct di_state *, size_t); @@ -320,6 +334,7 @@ static int di_cache_update(struct di_state *st); static void di_cache_print(di_cache_debug_t msglevel, char *fmt, ...); static int build_vhci_list(dev_info_t *vh_devinfo, void *arg); static int build_phci_list(dev_info_t *ph_devinfo, void *arg); +static void di_hotplug_children(struct di_state *st); extern int modrootloaded; extern void mdi_walk_vhcis(int (*)(dev_info_t *, void *), void *); @@ -1341,6 +1356,11 @@ di_snapshot(struct di_state *st) di_key_dtor, mod_hash_null_valdtor, di_hash_byptr, NULL, di_key_cmp, KM_SLEEP); + if (DINFOHP & st->command) { + list_create(&st->hp_list, sizeof (i_hp_t), + offsetof(i_hp_t, hp_link)); + } + /* * copy the device tree */ @@ -1350,6 +1370,10 @@ di_snapshot(struct di_state *st) mdi_walk_vhcis(build_vhci_list, st); } + if (DINFOHP & st->command) { + di_hotplug_children(st); + } + ddi_release_devi(rootnode); /* @@ -1696,7 +1720,8 @@ di_copynode(struct dev_info *node, struct di_stack *dsp, struct di_state *st) { di_off_t off; struct di_node *me; - size_t size; struct dev_info *n; + size_t size; + struct dev_info *n; dcmn_err2((CE_CONT, "di_copynode: depth = %x\n", dsp->depth)); ASSERT((node != NULL) && (node == TOP_NODE(dsp))); @@ -1820,7 +1845,7 @@ di_copynode(struct dev_info *node, struct di_stack *dsp, struct di_state *st) /* * An optimization to skip mutex_enter when not needed. */ - if (!((DINFOMINOR | DINFOPROP | DINFOPATH) & st->command)) { + if (!((DINFOMINOR | DINFOPROP | DINFOPATH | DINFOHP) & st->command)) { goto priv_data; } @@ -1873,7 +1898,7 @@ path: property: if (!(DINFOPROP & st->command)) { - goto priv_data; + goto hotplug_data; } if (node->devi_drv_prop_ptr) { /* driver property list */ @@ -1913,6 +1938,16 @@ property: } } +hotplug_data: + if (!(DINFOHP & st->command)) { + goto priv_data; + } + + if (node->devi_hp_hdlp) { /* hotplug data */ + me->hp_data = off; + off = di_gethpdata(node->devi_hp_hdlp, &me->hp_data, st); + } + priv_data: if (!(DINFOPRIVDATA & st->command)) { goto pm_info; @@ -3447,6 +3482,88 @@ di_getdpdata(struct dev_info *node, di_off_t *off_p, struct di_state *st) } /* + * Copy hotplug data associated with a devinfo node into the snapshot. + */ +static di_off_t +di_gethpdata(ddi_hp_cn_handle_t *hp_hdl, di_off_t *off_p, + struct di_state *st) +{ + struct i_hp *hp; + struct di_hp *me; + size_t size; + di_off_t off; + + dcmn_err2((CE_CONT, "di_gethpdata:\n")); + + /* + * check memory first + */ + off = di_checkmem(st, *off_p, sizeof (struct di_hp)); + *off_p = off; + + do { + me = DI_HP(di_mem_addr(st, off)); + me->self = off; + me->hp_name = 0; + me->hp_connection = (int)hp_hdl->cn_info.cn_num; + me->hp_depends_on = (int)hp_hdl->cn_info.cn_num_dpd_on; + (void) ddihp_cn_getstate(hp_hdl); + me->hp_state = (int)hp_hdl->cn_info.cn_state; + me->hp_type = (int)hp_hdl->cn_info.cn_type; + me->hp_type_str = 0; + me->hp_last_change = (uint32_t)hp_hdl->cn_info.cn_last_change; + me->hp_child = 0; + + /* + * Child links are resolved later by di_hotplug_children(). + * Store a reference to this di_hp_t in the list used later + * by di_hotplug_children(). + */ + hp = kmem_zalloc(sizeof (i_hp_t), KM_SLEEP); + hp->hp_off = off; + hp->hp_child = hp_hdl->cn_info.cn_child; + list_insert_tail(&st->hp_list, hp); + + off += sizeof (struct di_hp); + + /* Add name of this di_hp_t to the snapshot */ + if (hp_hdl->cn_info.cn_name) { + size = strlen(hp_hdl->cn_info.cn_name) + 1; + me->hp_name = off = di_checkmem(st, off, size); + (void) strcpy(di_mem_addr(st, off), + hp_hdl->cn_info.cn_name); + off += size; + } + + /* Add type description of this di_hp_t to the snapshot */ + if (hp_hdl->cn_info.cn_type_str) { + size = strlen(hp_hdl->cn_info.cn_type_str) + 1; + me->hp_type_str = off = di_checkmem(st, off, size); + (void) strcpy(di_mem_addr(st, off), + hp_hdl->cn_info.cn_type_str); + off += size; + } + + /* + * Set link to next in the chain of di_hp_t nodes, + * or terminate the chain when processing the last node. + */ + if (hp_hdl->next != NULL) { + off = di_checkmem(st, off, sizeof (struct di_hp)); + me->next = off; + } else { + me->next = 0; + } + + /* Update pointer to next in the chain */ + hp_hdl = hp_hdl->next; + + } while (hp_hdl); + + return (off); +} + +/* * The driver is stateful across DINFOCPYALL and DINFOUSRLD. * This function encapsulates the state machine: * @@ -4075,3 +4192,24 @@ di_cache_print(di_cache_debug_t msglevel, char *fmt, ...) vcmn_err(msglevel, fmt, ap); va_end(ap); } + +static void +di_hotplug_children(struct di_state *st) +{ + di_off_t off; + struct di_hp *hp; + struct i_hp *hp_list_node; + + while (hp_list_node = (struct i_hp *)list_remove_head(&st->hp_list)) { + + if ((hp_list_node->hp_child != NULL) && + (di_dip_find(st, hp_list_node->hp_child, &off) == 0)) { + hp = DI_HP(di_mem_addr(st, hp_list_node->hp_off)); + hp->hp_child = off; + } + + kmem_free(hp_list_node, sizeof (i_hp_t)); + } + + list_destroy(&st->hp_list); +} diff --git a/usr/src/uts/common/io/hotplug/pciehpc/pciehpc.c b/usr/src/uts/common/io/hotplug/pciehpc/pciehpc.c deleted file mode 100644 index 735e600c02..0000000000 --- a/usr/src/uts/common/io/hotplug/pciehpc/pciehpc.c +++ /dev/null @@ -1,1982 +0,0 @@ -/* - * 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 2008 Sun Microsystems, Inc. All rights reserved. - * Use is subject to license terms. - */ - -/* - * PCIEHPC - The Standard PCI Express HotPlug Controller driver module. This - * driver can be used with PCI Express HotPlug controllers that - * are compatible with the PCI Express ver 1.0a specification. - */ - -#include <sys/types.h> -#include <sys/note.h> -#include <sys/conf.h> -#include <sys/kmem.h> -#include <sys/debug.h> -#include <sys/vtrace.h> -#include <sys/modctl.h> -#include <sys/autoconf.h> -#include <sys/varargs.h> -#include <sys/ddi_impldefs.h> -#include <sys/pci.h> -#include <sys/time.h> -#include <sys/callb.h> -#include <sys/ddi.h> -#include <sys/sunddi.h> -#include <sys/pcie_impl.h> -#include <sys/hotplug/pci/pciehpc_impl.h> - -/* - * Local data/functions - */ - -/* mutex to protect pciehpc_head list */ -static kmutex_t pciehpc_list_mutex; - -/* pointer to linked list of pciehpc structures */ -static pciehpc_t *pciehpc_head = NULL; - -/* mutex to protect init/uninit controllers */ -static kmutex_t pciehpc_init_mutex; -static int pciehpc_init_count = 0; /* count of pciehpc instances in use */ - -static pciehpc_t *pciehpc_create_soft_state(dev_info_t *dip); -static pciehpc_t *pciehpc_get_soft_state(dev_info_t *dip); -static void pciehpc_destroy_soft_state(dev_info_t *dip); -static char *pciehpc_led_state_text(hpc_led_state_t state); -static void pciehpc_attn_btn_handler(pciehpc_t *ctrl_p); -static void pciehpc_dev_info(pciehpc_t *ctrl_p); - -static int pciehpc_pcie_dev(dev_info_t *dip, ddi_acc_handle_t handle); -static void pciehpc_disable_errors(pciehpc_t *ctrl_p); -static void pciehpc_enable_errors(pciehpc_t *ctrl_p); - -#ifdef DEBUG -int pciehpc_debug = 0; -static void pciehpc_dump_hpregs(pciehpc_t *ctrl_p); -#endif - -/* - * Module linkage information for the kernel. - */ -extern struct mod_ops mod_miscops; -static struct modlmisc modlmisc = -{ - &mod_miscops, - "PCIe hotplug driver", -}; - -static struct modlinkage modlinkage = -{ - MODREV_1, - &modlmisc, - NULL -}; - - -int -_init(void) -{ - int error; - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc: _init() called\n")); - mutex_init(&pciehpc_list_mutex, NULL, MUTEX_DRIVER, NULL); - mutex_init(&pciehpc_init_mutex, NULL, MUTEX_DRIVER, NULL); - if ((error = mod_install(&modlinkage)) != 0) { - mutex_destroy(&pciehpc_init_mutex); - mutex_destroy(&pciehpc_list_mutex); - } - - return (error); -} - -int -_fini(void) -{ - int error; - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc: _fini() called\n")); - - mutex_enter(&pciehpc_init_mutex); - if (pciehpc_init_count != 0) { - mutex_exit(&pciehpc_init_mutex); - return (EBUSY); - } - error = mod_remove(&modlinkage); - if (error != 0) { - mutex_exit(&pciehpc_init_mutex); - return (error); - } - mutex_destroy(&pciehpc_list_mutex); - mutex_destroy(&pciehpc_init_mutex); - return (0); -} - -int -_info(struct modinfo *modinfop) -{ - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc: _info() called\n")); - return (mod_info(&modlinkage, modinfop)); -} - -/* - * pciehpc_init() - * - * Initialize Hot Plug Controller if present. The arguments are: - * dip - Devinfo node pointer to the hot plug bus node - * regops - register ops to access HPC registers for non-standard - * HPC hw implementations (e.g: HPC in host PCI-E brdiges) - * This is NULL for standard HPC in PCIe bridges. - * Returns: - * DDI_SUCCESS for successful HPC initialization - * DDI_FAILURE for errors or if HPC hw not found - */ -int -pciehpc_init(dev_info_t *dip, pciehpc_regops_t *regops) -{ - pciehpc_t *ctrl_p; - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc_init() called (dip=%p)", - (void *)dip)); - - mutex_enter(&pciehpc_init_mutex); - - /* Make sure that it is not already initialized */ - if (pciehpc_get_soft_state(dip) != NULL) { - PCIEHPC_DEBUG((CE_WARN, - "%s%d: pciehpc instance already initialized!", - ddi_driver_name(dip), ddi_get_instance(dip))); - mutex_exit(&pciehpc_init_mutex); - return (DDI_SUCCESS); - } - - /* allocate a new soft state structure */ - ctrl_p = pciehpc_create_soft_state(dip); - - /* get PCI device info */ - pciehpc_dev_info(ctrl_p); - - /* setup access handle for HPC regs */ - if (regops != NULL) { - /* HPC access is non-standard; use the supplied reg ops */ - ctrl_p->regops = *regops; - } else { - /* standard HPC in a PCIe bridge */ - if (pciehpc_regs_setup(dip, 0, 0, &ctrl_p->regs_base, - &ctrl_p->cfghdl) != DDI_SUCCESS) - goto cleanup; - } - - pciehpc_disable_errors(ctrl_p); - - /* - * Set the platform specific hot plug mode. - */ - ctrl_p->hp_mode = PCIEHPC_NATIVE_HP_MODE; /* default is Native mode */ - ctrl_p->ops.init_hpc_hw = pciehpc_hpc_init; - ctrl_p->ops.init_hpc_slotinfo = pciehpc_slotinfo_init; - ctrl_p->ops.disable_hpc_intr = pciehpc_disable_intr; - ctrl_p->ops.enable_hpc_intr = pciehpc_enable_intr; - ctrl_p->ops.uninit_hpc_hw = pciehpc_hpc_uninit; - ctrl_p->ops.uninit_hpc_slotinfo = pciehpc_slotinfo_uninit; - ctrl_p->ops.probe_hpc = pciehpc_probe_hpc; - -#if defined(__i386) || defined(__amd64) - pciehpc_update_ops(ctrl_p); -#endif - if (regops == NULL) { /* it is a standard HPC in a PCIe bridge */ - /* make sure we really have a hot plug controller */ - if ((ctrl_p->ops.probe_hpc)(ctrl_p) != DDI_SUCCESS) - goto cleanup1; - } - - /* initialize hot plug controller hw */ - if ((ctrl_p->ops.init_hpc_hw)(ctrl_p) != DDI_SUCCESS) - goto cleanup1; - - /* initialize slot information soft state structure */ - if ((ctrl_p->ops.init_hpc_slotinfo)(ctrl_p) != DDI_SUCCESS) - goto cleanup2; - - /* register the hot plug slot with HPS framework */ - if (pciehpc_register_slot(ctrl_p) != DDI_SUCCESS) - goto cleanup3; - - /* HPC initialization is complete now */ - ctrl_p->soft_state |= PCIEHPC_SOFT_STATE_INITIALIZED; - ctrl_p->soft_state &= ~PCIEHPC_SOFT_STATE_UNINITIALIZED; - -#ifdef DEBUG - /* For debug, dump the HPC registers */ - if (pciehpc_debug > 2) - pciehpc_dump_hpregs(ctrl_p); -#endif - - /* enable hot plug interrupts/event */ - (void) (ctrl_p->ops.enable_hpc_intr)(ctrl_p); - - pciehpc_init_count++; - - mutex_exit(&pciehpc_init_mutex); - - return (DDI_SUCCESS); - -cleanup3: - (void) (ctrl_p->ops.uninit_hpc_slotinfo)(ctrl_p); - -cleanup2: - (void) (ctrl_p->ops.uninit_hpc_hw)(ctrl_p); - -cleanup1: - pciehpc_enable_errors(ctrl_p); - /* free up the HPC register mapping if applicable */ - if (ctrl_p->cfghdl) - pciehpc_regs_teardown(&ctrl_p->cfghdl); - -cleanup: - pciehpc_destroy_soft_state(dip); - mutex_exit(&pciehpc_init_mutex); - return (DDI_FAILURE); -} - -/* - * Uninitialize HPC soft state structure and free up any resources - * used for the HPC instance. - */ -int -pciehpc_uninit(dev_info_t *dip) -{ - pciehpc_t *ctrl_p; - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc_uninit() called (dip=%p)\n", - (void *)dip)); - - mutex_enter(&pciehpc_init_mutex); - - /* get the soft state structure for this dip */ - if ((ctrl_p = pciehpc_get_soft_state(dip)) == NULL) { - mutex_exit(&pciehpc_init_mutex); - return (DDI_FAILURE); - } - - /* disable interrupts */ - (void) (ctrl_p->ops.disable_hpc_intr)(ctrl_p); - - /* unregister the slot */ - (void) pciehpc_unregister_slot(ctrl_p); - - /* uninit any slot info data structures */ - (void) (ctrl_p->ops.uninit_hpc_slotinfo)(ctrl_p); - - /* uninitialize hpc, remove interrupt handler, etc. */ - (void) (ctrl_p->ops.uninit_hpc_hw)(ctrl_p); - - pciehpc_enable_errors(ctrl_p); - - /* free up the HPC register mapping if applicable */ - if (ctrl_p->cfghdl) - pciehpc_regs_teardown(&ctrl_p->cfghdl); - - /* destroy the soft state structure */ - pciehpc_destroy_soft_state(dip); - - ASSERT(pciehpc_init_count != 0); - - pciehpc_init_count--; - - mutex_exit(&pciehpc_init_mutex); - - return (DDI_SUCCESS); -} - -/* - * Probe for the inband PCI-E hot plug controller. Returns DDI_SUCCESS - * if found. This function works only for the standard PCI-E bridge - * that has inband hot plug controller. - * - * NOTE: This won't work for Host-PCIE bridges. - */ -int -pciehpc_probe_hpc(pciehpc_t *ctrl_p) -{ - uint8_t cap_ptr; - uint8_t cap_id; - uint16_t status; - - /* Read the PCI configuration status register. */ - status = pciehpc_reg_get16(ctrl_p, PCI_CONF_STAT); - - /* check for capabilities list */ - if (!(status & PCI_STAT_CAP)) { - /* no capabilities list */ - return (DDI_FAILURE); - } - - /* Get a pointer to the PCI capabilities list. */ - cap_ptr = pciehpc_reg_get8(ctrl_p, PCI_BCNF_CAP_PTR); - cap_ptr &= 0xFC; /* mask off reserved bits */ - - /* - * Walk thru the capabilities list looking for PCI Express capability - * structure. - */ - while (cap_ptr != PCI_CAP_NEXT_PTR_NULL) { - cap_id = pciehpc_reg_get8(ctrl_p, (uint_t)cap_ptr); - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc_probe_hpc() capability @" - " pointer=%02x (id=%02x)\n", cap_ptr, cap_id)); - - if (cap_id == PCI_CAP_ID_PCI_E) { - uint32_t slot_cap; - - /* Read the PCI Express Slot Capabilities Register */ - slot_cap = pciehpc_reg_get32(ctrl_p, - (uint_t)cap_ptr + PCIE_SLOTCAP); - - /* Does it have PCI Express HotPlug capability? */ - if (slot_cap & PCIE_SLOTCAP_HP_CAPABLE) { - /* Save the offset to PCI Express Capabilities structure */ - ctrl_p->pcie_caps_reg_offset = cap_ptr; - return (DDI_SUCCESS); - } - } - - /* Get the pointer to the next capability */ - cap_ptr = pciehpc_reg_get8(ctrl_p, (uint_t)cap_ptr + 1); - cap_ptr &= 0xFC; - } - - return (DDI_FAILURE); -} - -/* - * Setup slot information for use with HPS framework. - */ -int -pciehpc_slotinfo_init(pciehpc_t *ctrl_p) -{ - uint32_t slot_capabilities, link_capabilities; - pciehpc_slot_t *p = &ctrl_p->slot; - - /* - * setup HPS framework slot ops structure - */ - p->slot_ops.hpc_version = HPC_SLOT_OPS_VERSION; - p->slot_ops.hpc_op_connect = pciehpc_slot_connect; - p->slot_ops.hpc_op_disconnect = pciehpc_slot_disconnect; - p->slot_ops.hpc_op_insert = NULL; - p->slot_ops.hpc_op_remove = NULL; - p->slot_ops.hpc_op_control = pciehpc_slot_control; - - /* - * setup HPS framework slot information structure - */ - p->slot_info.version = HPC_SLOT_OPS_VERSION; - p->slot_info.slot_type = HPC_SLOT_TYPE_PCIE; - p->slot_info.slot_flags = - HPC_SLOT_CREATE_DEVLINK | HPC_SLOT_NO_AUTO_ENABLE; - p->slot_info.pci_slot_capabilities = HPC_SLOT_64BITS; - /* the device number is fixed as 0 as per the spec */ - p->slot_info.pci_dev_num = 0; - - /* read Slot Capabilities Register */ - slot_capabilities = pciehpc_reg_get32(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCAP); - - /* set slot-name/slot-number info */ - pciehpc_set_slot_name(ctrl_p); - - /* check if Attn Button present */ - ctrl_p->has_attn = (slot_capabilities & PCIE_SLOTCAP_ATTN_BUTTON) ? - B_TRUE : B_FALSE; - - /* check if Manual Retention Latch sensor present */ - ctrl_p->has_mrl = (slot_capabilities & PCIE_SLOTCAP_MRL_SENSOR) ? - B_TRUE : B_FALSE; - - /* - * PCI-E version 1.1 defines EMI Lock Present bit - * in Slot Capabilities register. Check for it. - */ - ctrl_p->has_emi_lock = (slot_capabilities & - PCIE_SLOTCAP_EMI_LOCK_PRESENT) ? B_TRUE : B_FALSE; - - link_capabilities = pciehpc_reg_get32(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_LINKCAP); - ctrl_p->dll_active_rep = (link_capabilities & - PCIE_LINKCAP_DLL_ACTIVE_REP_CAPABLE) ? B_TRUE : B_FALSE; - if (ctrl_p->dll_active_rep) - cv_init(&ctrl_p->slot.dll_active_cv, NULL, CV_DRIVER, NULL); - - /* initialize synchronization conditional variable */ - cv_init(&ctrl_p->slot.cmd_comp_cv, NULL, CV_DRIVER, NULL); - ctrl_p->slot.command_pending = B_FALSE; - - /* setup thread for handling ATTN button events */ - if (ctrl_p->has_attn) { - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_slotinfo_init: setting up ATTN button event " - "handler thread for slot %d\n", ctrl_p->slot.slotNum)); - cv_init(&ctrl_p->slot.attn_btn_cv, NULL, CV_DRIVER, NULL); - ctrl_p->slot.attn_btn_pending = B_FALSE; - ctrl_p->slot.attn_btn_threadp = thread_create(NULL, 0, - pciehpc_attn_btn_handler, - (void *)ctrl_p, 0, &p0, TS_RUN, minclsyspri); - ctrl_p->slot.attn_btn_thread_exit = B_FALSE; - } - - /* get current slot state from the hw */ - pciehpc_get_slot_state(ctrl_p); - - return (DDI_SUCCESS); -} - -/*ARGSUSED*/ -int -pciehpc_slotinfo_uninit(pciehpc_t *ctrl_p) -{ - cv_destroy(&ctrl_p->slot.cmd_comp_cv); - - if (ctrl_p->slot.attn_btn_threadp != NULL) { - mutex_enter(&ctrl_p->pciehpc_mutex); - ctrl_p->slot.attn_btn_thread_exit = B_TRUE; - cv_signal(&ctrl_p->slot.attn_btn_cv); - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_slotinfo_uninit: waiting for ATTN thread exit\n")); - cv_wait(&ctrl_p->slot.attn_btn_cv, &ctrl_p->pciehpc_mutex); - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_slotinfo_uninit: ATTN thread exit\n")); - cv_destroy(&ctrl_p->slot.attn_btn_cv); - ctrl_p->slot.attn_btn_threadp = NULL; - mutex_exit(&ctrl_p->pciehpc_mutex); - } - - if (ctrl_p->dll_active_rep) - cv_destroy(&ctrl_p->slot.dll_active_cv); - - return (DDI_SUCCESS); -} - -/* - * Get the current state of the slot from the hw. - */ -void -pciehpc_get_slot_state(pciehpc_t *ctrl_p) -{ - pciehpc_slot_t *p = &ctrl_p->slot; - uint16_t control, status; - - /* read the Slot Control Register */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - p->fault_led_state = HPC_LED_OFF; /* no fault led */ - p->active_led_state = HPC_LED_OFF; /* no active led */ - - /* read the current Slot Status Register */ - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - - /* get POWER led state */ - p->power_led_state = - pciehpc_led_state_to_hpc(pcie_slotctl_pwr_indicator_get(control)); - - /* get ATTN led state */ - p->attn_led_state = - pciehpc_led_state_to_hpc(pcie_slotctl_attn_indicator_get(control)); - - if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) - /* no device present; slot is empty */ - p->slot_state = HPC_SLOT_EMPTY; - - else if (!(control & PCIE_SLOTCTL_PWR_CONTROL)) - /* device is present and powered up */ - p->slot_state = HPC_SLOT_CONNECTED; - else - /* device is present and powered down */ - p->slot_state = HPC_SLOT_DISCONNECTED; -} - - -/* - * pciehpc_regs_setup() - * - * Setup PCI-E config registers for DDI access functions. - * - * Note: This is same as pci_config_setup() except that this may be - * used to map specific reg set with an offset in the case of host - * PCI-E bridges. - */ -int -pciehpc_regs_setup(dev_info_t *dip, uint_t rnum, offset_t off, - caddr_t *addrp, ddi_acc_handle_t *handle) -{ - ddi_device_acc_attr_t attr; - - attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; - attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; - attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; - - /* Check for fault management capabilities */ - if (DDI_FM_ACC_ERR_CAP(ddi_fm_capable(dip))) - attr.devacc_attr_access = DDI_FLAGERR_ACC; - - return (ddi_regs_map_setup(dip, rnum, addrp, off, 0, &attr, handle)); -} - -/* - * pciehpc_regs_teardown() - * - * Unmap config register set. - * - * Note: This is same as pci_config_teardown() function. - */ -void -pciehpc_regs_teardown(ddi_acc_handle_t *handle) -{ - ddi_regs_map_free(handle); -} - -/* - * Find the soft state structure for the HPC associated with the dip. - */ -static pciehpc_t * -pciehpc_get_soft_state(dev_info_t *dip) -{ - pciehpc_t *ctrl_p; - - mutex_enter(&pciehpc_list_mutex); - - ctrl_p = pciehpc_head; - - while (ctrl_p) { - if (ctrl_p->dip == dip) { - mutex_exit(&pciehpc_list_mutex); - return (ctrl_p); - } - ctrl_p = ctrl_p->nextp; - } - - mutex_exit(&pciehpc_list_mutex); - - return (NULL); -} - -/* - * Allocate a soft state structure for the HPC associated with this dip. - */ -static pciehpc_t * -pciehpc_create_soft_state(dev_info_t *dip) -{ - pciehpc_t *ctrl_p; - - ctrl_p = kmem_zalloc(sizeof (pciehpc_t), KM_SLEEP); - - ctrl_p->dip = dip; - - mutex_enter(&pciehpc_list_mutex); - ctrl_p->nextp = pciehpc_head; - pciehpc_head = ctrl_p; - ctrl_p->soft_state = PCIEHPC_SOFT_STATE_UNINITIALIZED; - mutex_exit(&pciehpc_list_mutex); - - return (ctrl_p); -} - -/* - * Remove the HPC soft state structure from the linked list. - */ -static void -pciehpc_destroy_soft_state(dev_info_t *dip) -{ - pciehpc_t **pp; - pciehpc_t *p; - - mutex_enter(&pciehpc_list_mutex); - pp = &pciehpc_head; - while ((p = *pp) != NULL) { - if (p->dip == dip) { - *pp = p->nextp; - kmem_free(p, sizeof (pciehpc_t)); - break; - } - pp = &(p->nextp); - } - mutex_exit(&pciehpc_list_mutex); -} - -/* - * convert LED state from PCIE HPC definition to hpc_led_state_t - * definition. - */ -hpc_led_state_t -pciehpc_led_state_to_hpc(uint16_t state) -{ - switch (state) { - case PCIE_SLOTCTL_INDICATOR_STATE_ON: - return (HPC_LED_ON); - case PCIE_SLOTCTL_INDICATOR_STATE_BLINK: - return (HPC_LED_BLINK); - case PCIE_SLOTCTL_INDICATOR_STATE_OFF: - default: - return (HPC_LED_OFF); - } -} - -/* - * convert LED state from hpc_led_state_t definition to PCIE HPC - * definition. - */ -uint16_t -pciehpc_led_state_to_pciehpc(hpc_led_state_t state) -{ - switch (state) { - case HPC_LED_ON: - return (PCIE_SLOTCTL_INDICATOR_STATE_ON); - case HPC_LED_BLINK: - return (PCIE_SLOTCTL_INDICATOR_STATE_BLINK); - case HPC_LED_OFF: - default: - return (PCIE_SLOTCTL_INDICATOR_STATE_OFF); - } -} - -/* - * Initialize HPC hardware, install interrupt handler, etc. It doesn't - * enable hot plug interrupts. - * - * (Note: It is called only from pciehpc_init().) - */ -int -pciehpc_hpc_init(pciehpc_t *ctrl_p) -{ - uint16_t reg; - - /* read the Slot Control Register */ - reg = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - /* disable all interrupts */ - reg &= ~(SLOTCTL_SUPPORTED_INTRS_MASK); - pciehpc_reg_put16(ctrl_p, ctrl_p->pcie_caps_reg_offset + - PCIE_SLOTCTL, reg); - - /* clear any interrupt status bits */ - reg = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS, reg); - - /* initialize the interrupt mutex */ - mutex_init(&ctrl_p->pciehpc_mutex, NULL, MUTEX_DRIVER, - (void *)PCIEHPC_INTR_PRI); - - return (DDI_SUCCESS); -} - -/* - * Uninitialize HPC hardware, uninstall interrupt handler, etc. - * - * (Note: It is called only from pciehpc_uninit().) - */ -int -pciehpc_hpc_uninit(pciehpc_t *ctrl_p) -{ - /* disable interrupts */ - (void) pciehpc_disable_intr(ctrl_p); - - /* destroy the mutex */ - mutex_destroy(&ctrl_p->pciehpc_mutex); - - return (DDI_SUCCESS); -} - -/* - * Disable hot plug interrupts. - * Note: this is only for Native hot plug mode. - */ -int -pciehpc_disable_intr(pciehpc_t *ctrl_p) -{ - uint16_t reg; - - /* read the Slot Control Register */ - reg = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - /* disable all interrupts */ - reg &= ~(SLOTCTL_SUPPORTED_INTRS_MASK); - pciehpc_reg_put16(ctrl_p, ctrl_p->pcie_caps_reg_offset + - PCIE_SLOTCTL, reg); - - /* clear any interrupt status bits */ - reg = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS, reg); - - return (DDI_SUCCESS); -} - -/* - * Enable hot plug interrupts. - * Note: this is only for Native hot plug mode. - */ -int -pciehpc_enable_intr(pciehpc_t *ctrl_p) -{ - uint16_t reg; - - /* clear any interrupt status bits */ - reg = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS, reg); - - /* read the Slot Control Register */ - reg = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - /* - * enable interrupts: power fault detection interrupt is enabled - * only when the slot is 'connected', i.e. power is ON - */ - if (ctrl_p->slot.slot_state == HPC_SLOT_CONNECTED) - pciehpc_reg_put16(ctrl_p, ctrl_p->pcie_caps_reg_offset + - PCIE_SLOTCTL, reg | SLOTCTL_SUPPORTED_INTRS_MASK); - else - pciehpc_reg_put16(ctrl_p, ctrl_p->pcie_caps_reg_offset + - PCIE_SLOTCTL, reg | (SLOTCTL_SUPPORTED_INTRS_MASK & - ~PCIE_SLOTCTL_PWR_FAULT_EN)); - - return (DDI_SUCCESS); -} - -/* - * Register the PCI-E hot plug slot with HPS framework. - */ -int -pciehpc_register_slot(pciehpc_t *ctrl_p) -{ - char nexus_path[MAXNAMELEN]; - pciehpc_slot_t *p = &ctrl_p->slot; - - /* get nexus path name */ - (void) ddi_pathname(ctrl_p->dip, nexus_path); - - /* register the slot with HPS framework */ - if (hpc_slot_register(ctrl_p->dip, nexus_path, - &p->slot_info, &p->slot_handle, - &p->slot_ops, (caddr_t)ctrl_p, 0) != 0) { - PCIEHPC_DEBUG((CE_WARN, - "pciehpc_register_slot() failed to register slot %d\n", - p->slotNum)); - return (DDI_FAILURE); - } - - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_register_slot(): registered slot %d\n", p->slotNum)); - return (DDI_SUCCESS); -} - -/* - * Unregister the PCI-E hot plug slot from the HPS framework. - */ -int -pciehpc_unregister_slot(pciehpc_t *ctrl_p) -{ - pciehpc_slot_t *p = &ctrl_p->slot; - - if (hpc_slot_unregister(&p->slot_handle) != 0) { - PCIEHPC_DEBUG((CE_WARN, - "pciehpc_unregister_slot() failed to unregister slot %d\n", - p->slotNum)); - return (DDI_FAILURE); - } - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_unregister_slot(): unregistered slot %d\n", p->slotNum)); - return (DDI_SUCCESS); -} - -/* - * pciehpc_intr() - * - * Interrupt handler for PCI-E Hot plug controller interrupts. - * - * Note: This is only for native mode hot plug. This is called - * by the nexus driver at interrupt context. Interrupt Service Routine - * registration is done by the nexus driver for both hot plug and - * non-hot plug interrupts. This function is called from the ISR - * of the nexus driver to handle hot-plug interrupts. - */ -int -pciehpc_intr(dev_info_t *dip) -{ - pciehpc_t *ctrl_p; - uint16_t status, control; - - /* get the soft state structure for this dip */ - if ((ctrl_p = pciehpc_get_soft_state(dip)) == NULL) - return (DDI_INTR_UNCLAIMED); - - mutex_enter(&ctrl_p->pciehpc_mutex); - - /* make sure the controller soft state is initialized */ - if (ctrl_p->soft_state & PCIEHPC_SOFT_STATE_UNINITIALIZED) { - mutex_exit(&ctrl_p->pciehpc_mutex); - return (DDI_INTR_UNCLAIMED); - } - - /* if it is not NATIVE hot plug mode then return */ - if (ctrl_p->hp_mode != PCIEHPC_NATIVE_HP_MODE) { - mutex_exit(&ctrl_p->pciehpc_mutex); - return (DDI_INTR_UNCLAIMED); - } - - /* read the current slot status register */ - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - - /* check if there are any hot plug interrupts occurred */ - if (!(status & SLOT_STATUS_EVENTS)) { - /* no hot plug events occurred */ - mutex_exit(&ctrl_p->pciehpc_mutex); - return (DDI_INTR_UNCLAIMED); - } - - /* clear the interrupt status bits */ - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS, status); - - /* check for CMD COMPLETE interrupt */ - if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) { - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_intr(): CMD COMPLETED interrupt received\n")); - /* wake up any one waiting for Command Completion event */ - cv_signal(&ctrl_p->slot.cmd_comp_cv); - } - - /* check for ATTN button interrupt */ - if (status & PCIE_SLOTSTS_ATTN_BTN_PRESSED) { - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_intr(): ATTN BUTTON interrupt received\n")); - /* if ATTN button event is still pending then cancel it */ - if (ctrl_p->slot.attn_btn_pending == B_TRUE) - ctrl_p->slot.attn_btn_pending = B_FALSE; - else - ctrl_p->slot.attn_btn_pending = B_TRUE; - /* wake up the ATTN event handler */ - cv_signal(&ctrl_p->slot.attn_btn_cv); - } - - /* check for power fault interrupt */ - if (status & PCIE_SLOTSTS_PWR_FAULT_DETECTED) { - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_intr(): POWER FAULT interrupt received" - " on slot %d\n", ctrl_p->slot.slotNum)); - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - if (control & PCIE_SLOTCTL_PWR_FAULT_EN) { - /* disable power fault detction interrupt */ - pciehpc_reg_put16(ctrl_p, ctrl_p->pcie_caps_reg_offset + - PCIE_SLOTCTL, control & ~PCIE_SLOTCTL_PWR_FAULT_EN); - - /* send the event to HPS framework */ - (void) hpc_slot_event_notify(ctrl_p->slot.slot_handle, - HPC_EVENT_SLOT_POWER_FAULT, HPC_EVENT_NORMAL); - } - } - - /* check for MRL SENSOR CHANGED interrupt */ - if (status & PCIE_SLOTSTS_MRL_SENSOR_CHANGED) { - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_intr(): MRL SENSOR CHANGED interrupt received" - " on slot %d\n", ctrl_p->slot.slotNum)); - /* For now (phase-I), no action is taken on this event */ - } - - /* check for PRESENCE CHANGED interrupt */ - if (status & PCIE_SLOTSTS_PRESENCE_CHANGED) { - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_intr(): PRESENCE CHANGED interrupt received" - " on slot %d\n", ctrl_p->slot.slotNum)); - - if (status & PCIE_SLOTSTS_PRESENCE_DETECTED) { - /* card is inserted into the slot */ - - /* send the event to HPS framework */ - (void) hpc_slot_event_notify(ctrl_p->slot.slot_handle, - HPC_EVENT_SLOT_INSERTION, HPC_EVENT_NORMAL); - } else { - /* card is removed from the slot */ - - /* make sure to disable power fault detction interrupt */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - if (control & PCIE_SLOTCTL_PWR_FAULT_EN) - pciehpc_reg_put16(ctrl_p, ctrl_p->pcie_caps_reg_offset + - PCIE_SLOTCTL, control & ~PCIE_SLOTCTL_PWR_FAULT_EN); - - /* send the event to HPS framework */ - (void) hpc_slot_event_notify(ctrl_p->slot.slot_handle, - HPC_EVENT_SLOT_REMOVAL, HPC_EVENT_NORMAL); - } - } - - /* check for DLL state changed interrupt */ - if (ctrl_p->dll_active_rep && - (status & PCIE_SLOTSTS_DLL_STATE_CHANGED)) { - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_intr(): DLL STATE CHANGED interrupt received" - " on slot %d\n", ctrl_p->slot.slotNum)); - - cv_signal(&ctrl_p->slot.dll_active_cv); - } - - mutex_exit(&ctrl_p->pciehpc_mutex); - - return (DDI_INTR_CLAIMED); -} - -#ifdef DEBUG -/* - * Dump PCI-E Hot Plug registers. - */ -static void -pciehpc_dump_hpregs(pciehpc_t *ctrl_p) -{ - uint16_t control; - uint32_t capabilities; - - capabilities = pciehpc_reg_get32(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCAP); - - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - cmn_err(CE_NOTE, "pciehpc_dump_hpregs: Found PCI-E hot plug slot %d\n", - ctrl_p->slot.slotNum); - cmn_err(CE_NOTE, "Attention Button Present = %s", - capabilities & PCIE_SLOTCAP_ATTN_BUTTON ? "Yes":"No"); - - cmn_err(CE_NOTE, "Power controller Present = %s", - capabilities & PCIE_SLOTCAP_POWER_CONTROLLER ? "Yes":"No"); - - cmn_err(CE_NOTE, "MRL Sensor Present = %s", - capabilities & PCIE_SLOTCAP_MRL_SENSOR ? "Yes":"No"); - - cmn_err(CE_NOTE, "Attn Indicator Present = %s", - capabilities & PCIE_SLOTCAP_ATTN_INDICATOR ? "Yes":"No"); - - cmn_err(CE_NOTE, "Power Indicator Present = %s", - capabilities & PCIE_SLOTCAP_PWR_INDICATOR ? "Yes":"No"); - - cmn_err(CE_NOTE, "HotPlug Surprise = %s", - capabilities & PCIE_SLOTCAP_HP_SURPRISE ? "Yes":"No"); - - cmn_err(CE_NOTE, "HotPlug Capable = %s", - capabilities & PCIE_SLOTCAP_HP_CAPABLE ? "Yes":"No"); - - cmn_err(CE_NOTE, "Physical Slot Number = %d", - PCIE_SLOTCAP_PHY_SLOT_NUM(capabilities)); - - cmn_err(CE_NOTE, "Attn Button interrupt Enabled = %s", - control & PCIE_SLOTCTL_ATTN_BTN_EN ? "Yes":"No"); - - cmn_err(CE_NOTE, "Power Fault interrupt Enabled = %s", - control & PCIE_SLOTCTL_PWR_FAULT_EN ? "Yes":"No"); - - cmn_err(CE_NOTE, "MRL Sensor INTR Enabled = %s", - control & PCIE_SLOTCTL_MRL_SENSOR_EN ? "Yes":"No"); - - cmn_err(CE_NOTE, "Presence interrupt Enabled = %s", - control & PCIE_SLOTCTL_PRESENCE_CHANGE_EN ? "Yes":"No"); - - cmn_err(CE_NOTE, "Cmd Complete interrupt Enabled = %s", - control & PCIE_SLOTCTL_CMD_INTR_EN ? "Yes":"No"); - - cmn_err(CE_NOTE, "HotPlug interrupt Enabled = %s", - control & PCIE_SLOTCTL_HP_INTR_EN ? "Yes":"No"); - - cmn_err(CE_NOTE, "Power Indicator LED = %s", pciehpc_led_state_text( - pciehpc_led_state_to_hpc(pcie_slotctl_pwr_indicator_get(control)))); - - cmn_err(CE_NOTE, "Attn Indicator LED = %s", - pciehpc_led_state_text(pciehpc_led_state_to_hpc( - pcie_slotctl_attn_indicator_get(control)))); -} - -static char * -pciehpc_led_state_text(hpc_led_state_t state) -{ - switch (state) { - case HPC_LED_ON: - return ("on"); - case HPC_LED_OFF: - return ("off"); - case HPC_LED_BLINK: - default: - return ("blink"); - } -} -#endif /* DEBUG */ - -/* - * pciehpc_slot_connect() - * - * Connect power to the PCI-E slot. - * - * Returns: HPC_SUCCESS if the slot is powered up and enabled. - * HPC_ERR_FAILED if the slot can't be enabled. - * - * (Note: This function is called by HPS framework at kernel context only.) - */ -/*ARGSUSED*/ -int -pciehpc_slot_connect(caddr_t ops_arg, hpc_slot_t slot_hdl, - void *data, uint_t flags) -{ - uint16_t status, control; - - pciehpc_t *ctrl_p = (pciehpc_t *)ops_arg; - - ASSERT(slot_hdl == ctrl_p->slot.slot_handle); - - mutex_enter(&ctrl_p->pciehpc_mutex); - - /* get the current state of the slot */ - pciehpc_get_slot_state(ctrl_p); - - /* check if the slot is already in the 'connected' state */ - if (ctrl_p->slot.slot_state == HPC_SLOT_CONNECTED) { - /* slot is already in the 'connected' state */ - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_slot_connect() slot %d already connected\n", - ctrl_p->slot.slotNum)); - mutex_exit(&ctrl_p->pciehpc_mutex); - return (HPC_SUCCESS); - } - - /* read the Slot Status Register */ - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - - /* make sure the MRL switch is closed if present */ - if ((ctrl_p->has_mrl) && (status & PCIE_SLOTSTS_MRL_SENSOR_OPEN)) { - /* MRL switch is open */ - cmn_err(CE_WARN, "MRL switch is open on slot %d\n", - ctrl_p->slot.slotNum); - goto cleanup; - } - - /* make sure the slot has a device present */ - if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) { - /* slot is empty */ - PCIEHPC_DEBUG((CE_NOTE, - "slot %d is empty\n", ctrl_p->slot.slotNum)); - goto cleanup; - } - - /* get the current state of Slot Control Register */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - /* check if the slot's power state is ON */ - if (!(control & PCIE_SLOTCTL_PWR_CONTROL)) { - /* slot is already powered up */ - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_slot_connect() slot %d already connected\n", - ctrl_p->slot.slotNum)); - ctrl_p->slot.slot_state = HPC_SLOT_CONNECTED; - mutex_exit(&ctrl_p->pciehpc_mutex); - return (HPC_SUCCESS); - } - - /* - * Enable power to the slot involves: - * 1. Set power LED to blink and ATTN led to OFF. - * 2. Set power control ON in Slot Control Reigster and - * wait for Command Completed Interrupt or 1 sec timeout. - * 3. If Data Link Layer State Changed events are supported - * then wait for the event to indicate Data Layer Link - * is active. The time out value for this event is 1 second. - * This is specified in PCI-E version 1.1. - * 4. Set power LED to be ON. - */ - - /* 1. set power LED to blink & ATTN led to OFF */ - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, HPC_LED_BLINK); - pciehpc_set_led_state(ctrl_p, HPC_ATTN_LED, HPC_LED_OFF); - - /* 2. set power control to ON */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - control &= ~PCIE_SLOTCTL_PWR_CONTROL; - pciehpc_issue_hpc_command(ctrl_p, control); - - /* 3. wait for DLL State Change event, if it's supported */ - if (ctrl_p->dll_active_rep) { - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_LINKSTS); - - if (!(status & PCIE_LINKSTS_DLL_LINK_ACTIVE)) { - /* wait 1 sec for the DLL State Changed event */ - (void) cv_timedwait(&ctrl_p->slot.dll_active_cv, - &ctrl_p->pciehpc_mutex, - ddi_get_lbolt() + - SEC_TO_TICK(PCIEHPC_DLL_STATE_CHANGE_TIMEOUT)); - - /* check Link status */ - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + - PCIE_LINKSTS); - if (!(status & PCIE_LINKSTS_DLL_LINK_ACTIVE)) - goto cleanup2; - } - } - - /* wait 1 sec for link to come up */ - delay(drv_usectohz(1000000)); - - /* check power is really turned ON */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - if (control & PCIE_SLOTCTL_PWR_CONTROL) { - PCIEHPC_DEBUG((CE_NOTE, - "slot %d fails to turn on power on connect\n", - ctrl_p->slot.slotNum)); - - goto cleanup1; - } - - /* clear power fault status */ - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - status |= PCIE_SLOTSTS_PWR_FAULT_DETECTED; - pciehpc_reg_put16(ctrl_p, ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS, - status); - - /* enable power fault detection interrupt */ - control |= PCIE_SLOTCTL_PWR_FAULT_EN; - pciehpc_issue_hpc_command(ctrl_p, control); - - /* 4. Set power LED to be ON */ - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, HPC_LED_ON); - - /* if EMI is present, turn it ON */ - if (ctrl_p->has_emi_lock) { - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - - if (!(status & PCIE_SLOTSTS_EMI_LOCK_SET)) { - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - control |= PCIE_SLOTCTL_EMI_LOCK_CONTROL; - pciehpc_issue_hpc_command(ctrl_p, control); - - /* wait 1 sec after toggling the state of EMI lock */ - delay(drv_usectohz(1000000)); - } - } - - ctrl_p->slot.slot_state = HPC_SLOT_CONNECTED; - mutex_exit(&ctrl_p->pciehpc_mutex); - return (HPC_SUCCESS); - -cleanup2: - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - /* if power is ON, set power control to OFF */ - if (!(control & PCIE_SLOTCTL_PWR_CONTROL)) { - control |= PCIE_SLOTCTL_PWR_CONTROL; - pciehpc_issue_hpc_command(ctrl_p, control); - } - -cleanup1: - /* set power led to OFF */ - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, HPC_LED_OFF); - -cleanup: - mutex_exit(&ctrl_p->pciehpc_mutex); - return (HPC_ERR_FAILED); -} - -/* - * pciehpc_slot_disconnect() - * - * Disconnect power to the slot. - * - * Returns: HPC_SUCCESS if the slot is powered up and enabled. - * HPC_ERR_FAILED if the slot can't be enabled. - * - * (Note: This function is called by HPS framework at kernel context only.) - */ -/*ARGSUSED*/ -int -pciehpc_slot_disconnect(caddr_t ops_arg, hpc_slot_t slot_hdl, - void *data, uint_t flags) -{ - uint16_t status; - uint16_t control; - - pciehpc_t *ctrl_p = (pciehpc_t *)ops_arg; - - ASSERT(slot_hdl == ctrl_p->slot.slot_handle); - - mutex_enter(&ctrl_p->pciehpc_mutex); - - /* get the current state of the slot */ - pciehpc_get_slot_state(ctrl_p); - - /* check if the slot is already in the 'disconnected' state */ - if (ctrl_p->slot.slot_state == HPC_SLOT_DISCONNECTED) { - /* slot is in the 'disconnected' state */ - PCIEHPC_DEBUG3((CE_NOTE, - "pciehpc_slot_disconnect(): slot %d already disconnected\n", - ctrl_p->slot.slotNum)); - ASSERT(ctrl_p->slot.power_led_state == HPC_LED_OFF); - mutex_exit(&ctrl_p->pciehpc_mutex); - return (HPC_SUCCESS); - } - - /* read the Slot Status Register */ - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - - /* make sure the slot has a device present */ - if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) { - /* slot is empty */ - PCIEHPC_DEBUG((CE_NOTE, - "pciehpc_slot_disconnect(): slot %d is empty\n", - ctrl_p->slot.slotNum)); - goto cleanup; - } - - /* - * Disable power to the slot involves: - * 1. Set power LED to blink. - * 2. Set power control OFF in Slot Control Reigster and - * wait for Command Completed Interrupt or 1 sec timeout. - * 3. Set POWER led and ATTN led to be OFF. - */ - - /* 1. set power LED to blink */ - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, HPC_LED_BLINK); - - /* disable power fault detection interrupt */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - control &= ~PCIE_SLOTCTL_PWR_FAULT_EN; - pciehpc_issue_hpc_command(ctrl_p, control); - - /* 2. set power control to OFF */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - control |= PCIE_SLOTCTL_PWR_CONTROL; - pciehpc_issue_hpc_command(ctrl_p, control); - -#ifdef DEBUG - /* check for power control bit to be OFF */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - ASSERT(control & PCIE_SLOTCTL_PWR_CONTROL); -#endif - - /* 3. Set power LED to be OFF */ - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, HPC_LED_OFF); - pciehpc_set_led_state(ctrl_p, HPC_ATTN_LED, HPC_LED_OFF); - - /* if EMI is present, turn it OFF */ - if (ctrl_p->has_emi_lock) { - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - - if (status & PCIE_SLOTSTS_EMI_LOCK_SET) { - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - control |= PCIE_SLOTCTL_EMI_LOCK_CONTROL; - pciehpc_issue_hpc_command(ctrl_p, control); - - /* wait 1 sec after toggling the state of EMI lock */ - delay(drv_usectohz(1000000)); - } - } - - ctrl_p->slot.slot_state = HPC_SLOT_DISCONNECTED; - mutex_exit(&ctrl_p->pciehpc_mutex); - return (HPC_SUCCESS); - -cleanup: - mutex_exit(&ctrl_p->pciehpc_mutex); - return (HPC_ERR_FAILED); -} - -/*ARGSUSED*/ -int -pciehpc_slot_control(caddr_t ops_arg, hpc_slot_t slot_hdl, - int request, caddr_t arg) -{ - pciehpc_t *ctrl_p; - hpc_led_info_t *led_info; - int ret = HPC_SUCCESS; - - ctrl_p = (pciehpc_t *)ops_arg; - - ASSERT(ctrl_p != NULL); - - mutex_enter(&ctrl_p->pciehpc_mutex); - - /* get the current slot state */ - pciehpc_get_slot_state(ctrl_p); - - switch (request) { - - case HPC_CTRL_GET_SLOT_STATE: - *(hpc_slot_state_t *)arg = ctrl_p->slot.slot_state; - break; - - case HPC_CTRL_GET_BOARD_TYPE: - if (ctrl_p->slot.slot_state == HPC_SLOT_EMPTY) - *(hpc_board_type_t *)arg = HPC_BOARD_UNKNOWN; - else - *(hpc_board_type_t *)arg = HPC_BOARD_PCI_HOTPLUG; - break; - - case HPC_CTRL_GET_LED_STATE: - led_info = (hpc_led_info_t *)arg; - switch (led_info->led) { - case HPC_ATTN_LED: - led_info->state = ctrl_p->slot.attn_led_state; - break; - case HPC_POWER_LED: - led_info->state = ctrl_p->slot.power_led_state; - break; - case HPC_FAULT_LED: - case HPC_ACTIVE_LED: - led_info->state = HPC_LED_OFF; - break; - default: - PCIEHPC_DEBUG((CE_WARN, "pciehpc_slot_control:" - " unknown led state\n")); - ret = HPC_ERR_NOTSUPPORTED; - break; - } - break; - case HPC_CTRL_SET_LED_STATE: - led_info = (hpc_led_info_t *)arg; - switch (led_info->led) { - case HPC_ATTN_LED: - pciehpc_set_led_state(ctrl_p, led_info->led, - led_info->state); - break; - case HPC_POWER_LED: - PCIEHPC_DEBUG((CE_WARN, "pciehpc_slot_control: power" - " LED control is not allowed on slot #%d\n", - ctrl_p->slot.slotNum)); - ret = HPC_ERR_NOTSUPPORTED; - break; - case HPC_FAULT_LED: - case HPC_ACTIVE_LED: - break; - default: - PCIEHPC_DEBUG((CE_WARN, "pciehpc_slot_control:" - " unknown led type %d\n", led_info->led)); - ret = HPC_ERR_NOTSUPPORTED; - break; - } - break; - case HPC_CTRL_DEV_CONFIG_FAILURE: - /* turn the ATTN led ON for configure failure */ - pciehpc_set_led_state(ctrl_p, HPC_ATTN_LED, HPC_LED_ON); - /* if power to the slot is still on then set Power led to ON */ - if (ctrl_p->slot.slot_state == HPC_SLOT_CONNECTED) - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, HPC_LED_ON); - break; - case HPC_CTRL_DEV_UNCONFIG_FAILURE: - /* if power to the slot is still on then set Power led to ON */ - if (ctrl_p->slot.slot_state == HPC_SLOT_CONNECTED) - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, HPC_LED_ON); - pciehpc_enable_errors(ctrl_p); - break; - case HPC_CTRL_ENABLE_AUTOCFG: - case HPC_CTRL_DISABLE_AUTOCFG: - /* no action is needed here */ - break; - - case HPC_CTRL_DISABLE_SLOT: - case HPC_CTRL_ENABLE_SLOT: - /* no action is needed here */ - break; - - case HPC_CTRL_DEV_CONFIG_START: - case HPC_CTRL_DEV_UNCONFIG_START: - pciehpc_disable_errors(ctrl_p); - /* no action is needed here */ - break; - case HPC_CTRL_DEV_CONFIGURED: - case HPC_CTRL_DEV_UNCONFIGURED: - /* no action is needed here */ - if (request == HPC_CTRL_DEV_CONFIGURED) { - pciehpc_enable_errors(ctrl_p); - } - break; - default: - PCIEHPC_DEBUG((CE_WARN, - "pciehpc_slot_control: unsupported operation\n")); - ret = HPC_ERR_NOTSUPPORTED; - } - - mutex_exit(&ctrl_p->pciehpc_mutex); - - return (ret); -} - -/* - * Get the state of an LED. - */ -hpc_led_state_t -pciehpc_get_led_state(pciehpc_t *ctrl_p, hpc_led_t led) -{ - uint16_t control; - uint16_t state; - - /* get the current state of Slot Control register */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - switch (led) { - case HPC_POWER_LED: - state = pcie_slotctl_pwr_indicator_get(control); - break; - case HPC_ATTN_LED: - state = pcie_slotctl_attn_indicator_get(control); - break; - default: - PCIEHPC_DEBUG((CE_WARN, - "pciehpc_get_led_state() invalid LED %d\n", led)); - return (HPC_LED_OFF); - } - - switch (state) { - case PCIE_SLOTCTL_INDICATOR_STATE_ON: - return (HPC_LED_ON); - - case PCIE_SLOTCTL_INDICATOR_STATE_BLINK: - return (HPC_LED_BLINK); - - case PCIE_SLOTCTL_INDICATOR_STATE_OFF: - default: - return (HPC_LED_OFF); - } -} - -/* - * Set the state of an LED. It updates both hw and sw state. - */ -void -pciehpc_set_led_state(pciehpc_t *ctrl_p, hpc_led_t led, hpc_led_state_t state) -{ - uint16_t control; - - /* get the current state of Slot Control register */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - switch (led) { - case HPC_POWER_LED: - /* clear led mask */ - control &= ~PCIE_SLOTCTL_PWR_INDICATOR_MASK; - ctrl_p->slot.power_led_state = state; - break; - case HPC_ATTN_LED: - /* clear led mask */ - control &= ~PCIE_SLOTCTL_ATTN_INDICATOR_MASK; - ctrl_p->slot.attn_led_state = state; - break; - default: - PCIEHPC_DEBUG((CE_WARN, - "pciehpc_set_led_state() invalid LED %d\n", led)); - return; - } - - switch (state) { - case HPC_LED_ON: - if (led == HPC_POWER_LED) - control = pcie_slotctl_pwr_indicator_set(control, - PCIE_SLOTCTL_INDICATOR_STATE_ON); - else if (led == HPC_ATTN_LED) - control = pcie_slotctl_attn_indicator_set(control, - PCIE_SLOTCTL_INDICATOR_STATE_ON); - break; - case HPC_LED_OFF: - if (led == HPC_POWER_LED) - control = pcie_slotctl_pwr_indicator_set(control, - PCIE_SLOTCTL_INDICATOR_STATE_OFF); - else if (led == HPC_ATTN_LED) - control = pcie_slotctl_attn_indicator_set(control, - PCIE_SLOTCTL_INDICATOR_STATE_OFF); - break; - case HPC_LED_BLINK: - if (led == HPC_POWER_LED) - control = pcie_slotctl_pwr_indicator_set(control, - PCIE_SLOTCTL_INDICATOR_STATE_BLINK); - else if (led == HPC_ATTN_LED) - control = pcie_slotctl_attn_indicator_set(control, - PCIE_SLOTCTL_INDICATOR_STATE_BLINK); - break; - - default: - PCIEHPC_DEBUG((CE_WARN, - "pciehpc_set_led_state() invalid LED state %d\n", state)); - return; - } - - /* update the Slot Control Register */ - pciehpc_issue_hpc_command(ctrl_p, control); - -#ifdef DEBUG - /* get the current state of Slot Control register */ - control = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL); - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc_set_led_state: " - "slot %d power-led %s attn-led %s\n", - ctrl_p->slot.slotNum, - pciehpc_led_state_text( - pciehpc_led_state_to_hpc( - pcie_slotctl_pwr_indicator_get(control))), - pciehpc_led_state_text( - pciehpc_led_state_to_hpc( - pcie_slotctl_attn_indicator_get(control))))); -#endif -} - -/* - * Send a command to the PCI-E Hot Plug Controller. - * - * NOTES: The PCI-E spec defines the following semantics for issuing hot plug - * commands. - * 1) If Command Complete events/interrupts are supported then software - * waits for Command Complete event after issuing a command (i.e writing - * to the Slot Control register). The command completion could take as - * long as 1 second so software should be prepared to wait for 1 second - * before issuing another command. - * - * 2) If Command Complete events/interrupts are not supported then - * software could issue multiple Slot Control writes without any delay - * between writes. - */ -void -pciehpc_issue_hpc_command(pciehpc_t *ctrl_p, uint16_t control) -{ - - uint16_t status; - uint32_t slot_cap; - - /* - * PCI-E version 1.1 spec defines No Command Completed - * Support bit (bit#18) in Slot Capabilities register. If this - * bit is set then slot doesn't support notification of command - * completion events. - */ - slot_cap = pciehpc_reg_get32(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCAP); - /* - * If no Command Completion event is supported or it is ACPI - * hot plug mode then just issue the command and return. - */ - if ((slot_cap & PCIE_SLOTCAP_NO_CMD_COMP_SUPP) || - (ctrl_p->hp_mode == PCIEHPC_ACPI_HP_MODE)) { - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL, control); - return; - } - - /* - * ************************************** - * Command Complete events are supported. - * ************************************** - */ - - /* - * If HPC is not yet initialized then just poll for the Command - * Completion interrupt. - */ - if (!(ctrl_p->soft_state & PCIEHPC_SOFT_STATE_INITIALIZED)) { - int retry = PCIEHPC_CMD_WAIT_RETRY; - - /* write the command to the HPC */ - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL, - control); - - /* poll for status completion */ - while (retry--) { - /* wait for 10 msec before checking the status */ - delay(drv_usectohz(PCIEHPC_CMD_WAIT_TIME)); - - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - - if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) { - /* clear the status bits */ - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS, - status); - break; - } - } - return; - } - - /* HPC is already initialized */ - - ASSERT(MUTEX_HELD(&ctrl_p->pciehpc_mutex)); - - /* - * If previous command is still pending then wait for its - * completion. i.e cv_wait() - */ - - while (ctrl_p->slot.command_pending == B_TRUE) - cv_wait(&ctrl_p->slot.cmd_comp_cv, &ctrl_p->pciehpc_mutex); - - /* - * Issue the command and wait for Command Completion or - * the 1 sec timeout. - */ - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCTL, control); - - ctrl_p->slot.command_pending = B_TRUE; - - if (cv_timedwait(&ctrl_p->slot.cmd_comp_cv, &ctrl_p->pciehpc_mutex, - ddi_get_lbolt() + SEC_TO_TICK(1)) == -1) { - /* it is a timeout */ - PCIEHPC_DEBUG2((CE_NOTE, - "pciehpc_issue_hpc_command: Command Complete" - " interrupt is not received for slot %d\n", - ctrl_p->slot.slotNum)); - - /* clear the status info in case interrupts are disabled? */ - status = pciehpc_reg_get16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS); - - if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) { - /* clear the status bits */ - pciehpc_reg_put16(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTSTS, - status); - } - } - - ctrl_p->slot.command_pending = B_FALSE; - - /* wake up any one waiting for issuing another command to HPC */ - cv_signal(&ctrl_p->slot.cmd_comp_cv); -} - -/* - * pciehcp_attn_btn_handler() - * - * This handles ATTN button pressed event as per the PCI-E 1.1 spec. - */ -static void -pciehpc_attn_btn_handler(pciehpc_t *ctrl_p) -{ - hpc_led_state_t power_led_state; - callb_cpr_t cprinfo; - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc_attn_btn_handler: thread started\n")); - - CALLB_CPR_INIT(&cprinfo, &ctrl_p->pciehpc_mutex, callb_generic_cpr, - "pciehpc_attn_btn_handler"); - - mutex_enter(&ctrl_p->pciehpc_mutex); - - /* wait for ATTN button event */ - cv_wait(&ctrl_p->slot.attn_btn_cv, &ctrl_p->pciehpc_mutex); - - while (ctrl_p->slot.attn_btn_thread_exit == B_FALSE) { - - if (ctrl_p->slot.attn_btn_pending == B_TRUE) { - - /* get the current state of power LED */ - power_led_state = pciehpc_get_led_state(ctrl_p, HPC_POWER_LED); - - /* Blink the Power LED while we wait for 5 seconds */ - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, HPC_LED_BLINK); - - /* wait for 5 seconds before taking any action */ - if (cv_timedwait(&ctrl_p->slot.attn_btn_cv, - &ctrl_p->pciehpc_mutex, - ddi_get_lbolt() + SEC_TO_TICK(5)) == -1) { - /* - * It is a time out; make sure the ATTN pending flag is - * still ON before sending the event to HPS framework. - */ - if (ctrl_p->slot.attn_btn_pending == B_TRUE) { - /* send the ATTN button event to HPS framework */ - ctrl_p->slot.attn_btn_pending = B_FALSE; - (void) hpc_slot_event_notify( - ctrl_p->slot.slot_handle, - HPC_EVENT_SLOT_ATTN, HPC_EVENT_NORMAL); - } - } - /* restore the power LED state */ - pciehpc_set_led_state(ctrl_p, HPC_POWER_LED, power_led_state); - continue; - } - - /* wait for another ATTN button event */ - cv_wait(&ctrl_p->slot.attn_btn_cv, &ctrl_p->pciehpc_mutex); - } - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc_attn_btn_handler: thread exit\n")); - cv_signal(&ctrl_p->slot.attn_btn_cv); - CALLB_CPR_EXIT(&cprinfo); - thread_exit(); -} - -/* - * Read/Write access to HPC registers. If platform nexus has non-standard - * HPC access mechanism then regops functions are used to do reads/writes. - */ -uint8_t -pciehpc_reg_get8(pciehpc_t *ctrl_p, uint_t off) -{ - PCIEHPC_DEBUG3((CE_NOTE, "read reg8 (offset %x)", off)); - - if (ctrl_p->regops.get != NULL) - return ((uint8_t)ctrl_p->regops.get(ctrl_p->regops.cookie, - (off_t)off)); - else - return (ddi_get8(ctrl_p->cfghdl, - (uint8_t *)(ctrl_p->regs_base + off))); -} - -uint16_t -pciehpc_reg_get16(pciehpc_t *ctrl_p, uint_t off) -{ - PCIEHPC_DEBUG3((CE_NOTE, "read reg16 (offset %x)", off)); - - if (ctrl_p->regops.get != NULL) - return ((uint16_t)ctrl_p->regops.get(ctrl_p->regops.cookie, - (off_t)off)); - else - return (ddi_get16(ctrl_p->cfghdl, - (uint16_t *)(ctrl_p->regs_base + off))); -} - -uint32_t -pciehpc_reg_get32(pciehpc_t *ctrl_p, uint_t off) -{ - PCIEHPC_DEBUG3((CE_NOTE, "read reg32 (offset %x)", off)); - - if (ctrl_p->regops.get != NULL) - return ((uint32_t)ctrl_p->regops.get(ctrl_p->regops.cookie, - (off_t)off)); - else - return (ddi_get32(ctrl_p->cfghdl, - (uint32_t *)(ctrl_p->regs_base + off))); -} - -void -pciehpc_reg_put8(pciehpc_t *ctrl_p, uint_t off, uint8_t val) -{ - PCIEHPC_DEBUG3((CE_NOTE, "write reg8 (offset %x, val %x)", - off, val)); - - if (ctrl_p->regops.put != NULL) - ctrl_p->regops.put(ctrl_p->regops.cookie, (off_t)off, (uint_t)val); - else - ddi_put8(ctrl_p->cfghdl, - (uint8_t *)(ctrl_p->regs_base + off), val); -} - -void -pciehpc_reg_put16(pciehpc_t *ctrl_p, uint_t off, uint16_t val) -{ - PCIEHPC_DEBUG3((CE_NOTE, "write reg16 (offset %x, val %x)", - off, val)); - - if (ctrl_p->regops.put != NULL) - ctrl_p->regops.put(ctrl_p->regops.cookie, (off_t)off, (uint_t)val); - else - ddi_put16(ctrl_p->cfghdl, - (uint16_t *)(ctrl_p->regs_base + off), val); -} - -void -pciehpc_reg_put32(pciehpc_t *ctrl_p, uint_t off, uint32_t val) -{ - PCIEHPC_DEBUG3((CE_NOTE, "write reg32 (offset %x, val %x)", - off, val)); - - if (ctrl_p->regops.put != NULL) - ctrl_p->regops.put(ctrl_p->regops.cookie, (off_t)off, (uint_t)val); - else - ddi_put32(ctrl_p->cfghdl, - (uint32_t *)(ctrl_p->regs_base + off), val); -} - -static void -pciehpc_dev_info(pciehpc_t *ctrl_p) -{ - pci_regspec_t *regspec; - int reglen; - dev_info_t *dip = ctrl_p->dip; - - /* - * Check if it is a PCIe fabric hotplug nexus. This is specially - * not so for Rootcomplex nodes supporting PCIe hotplug. - * We save this information so as to implement hardening for - * fabric nodes only via pcie services. - */ - if (pciehpc_pcie_dev(dip, ctrl_p->cfghdl) == DDI_SUCCESS) - ctrl_p->soft_state |= PCIEHPC_SOFT_STATE_PCIE_DEV; - - /* Get the device number. */ - if (ddi_getlongprop(DDI_DEV_T_NONE, dip, DDI_PROP_DONTPASS, - "reg", (caddr_t)®spec, ®len) != DDI_SUCCESS) { - return; - } - - ctrl_p->bus = PCI_REG_BUS_G(regspec[0].pci_phys_hi); - ctrl_p->dev = PCI_REG_DEV_G(regspec[0].pci_phys_hi); - ctrl_p->func = PCI_REG_FUNC_G(regspec[0].pci_phys_hi); - - kmem_free(regspec, reglen); - - PCIEHPC_DEBUG3((CE_NOTE, "pciehpc_dev_info: bus=%x, dev=%x, func=%x", - ctrl_p->bus, ctrl_p->dev, ctrl_p->func)); -} - -/* - * setup slot name/slot-number info. - */ -void -pciehpc_set_slot_name(pciehpc_t *ctrl_p) -{ - pciehpc_slot_t *p = &ctrl_p->slot; - uchar_t *slotname_data; - int *slotnum; - uint_t count; - int len; - int invalid_slotnum = 0; - uint32_t slot_capabilities; - - if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ctrl_p->dip, - DDI_PROP_DONTPASS, "physical-slot#", &slotnum, &count) == - DDI_PROP_SUCCESS) { - p->slotNum = slotnum[0]; - ddi_prop_free(slotnum); - } else { - slot_capabilities = pciehpc_reg_get32(ctrl_p, - ctrl_p->pcie_caps_reg_offset + PCIE_SLOTCAP); - p->slotNum = PCIE_SLOTCAP_PHY_SLOT_NUM(slot_capabilities); - } - - if (!p->slotNum) { /* platform may not have initialized it */ - PCIEHPC_DEBUG((CE_WARN, "%s#%d: Invalid slot number! ", - ddi_driver_name(ctrl_p->dip), - ddi_get_instance(ctrl_p->dip))); - p->slotNum = pciehpc_reg_get8(ctrl_p, PCI_BCNF_SECBUS); - invalid_slotnum = 1; - } - - /* - * construct the slot_name: - * if "slot-names" property exists then use that name - * else if valid slot number exists then it is "pcie<slot-num>". - * else it will be "pcie<sec-bus-number>dev0" - */ - if (ddi_getlongprop(DDI_DEV_T_ANY, ctrl_p->dip, DDI_PROP_DONTPASS, - "slot-names", (caddr_t)&slotname_data, - &len) == DDI_PROP_SUCCESS) { - /* - * Note: for PCI-E slots, the device number is always 0 so the - * first (and only) string is the slot name for this slot. - */ - (void) sprintf(p->slot_info.pci_slot_name, - (char *)slotname_data + 4); - kmem_free(slotname_data, len); - } else { - if (invalid_slotnum) /* use device number ie. 0 */ - (void) snprintf(p->slot_info.pci_slot_name, - sizeof (p->slot_info.pci_slot_name), "pcie0"); - else - (void) snprintf(p->slot_info.pci_slot_name, - sizeof (p->slot_info.pci_slot_name), "pcie%d", - p->slotNum); - } -} - -/*ARGSUSED*/ -static int -pciehpc_pcie_dev(dev_info_t *dip, ddi_acc_handle_t handle) -{ - /* get parent device's device_type property */ - char *device_type; - int rc; - dev_info_t *pdip = ddi_get_parent(dip); - - if (ddi_prop_lookup_string(DDI_DEV_T_ANY, pdip, - DDI_PROP_DONTPASS, "device_type", &device_type) - != DDI_PROP_SUCCESS) { - PCIEHPC_DEBUG2((CE_NOTE, "device_type property missing for " - "%s#%d", ddi_get_name(pdip), ddi_get_instance(pdip))); - return (DDI_FAILURE); - } - - PCIEHPC_DEBUG((CE_NOTE, "device_type=<%s>\n", device_type)); - rc = DDI_FAILURE; - if (strcmp(device_type, "pciex") == 0) - rc = DDI_SUCCESS; - ddi_prop_free(device_type); - return (rc); -} - -static void -pciehpc_disable_errors(pciehpc_t *ctrl_p) -{ - if (ctrl_p->soft_state & PCIEHPC_SOFT_STATE_PCIE_DEV) { - PCIE_DISABLE_ERRORS(ctrl_p->dip); - PCIEHPC_DEBUG3((CE_NOTE, "%s%d: pciehpc_disable_errors\n", - ddi_driver_name(ctrl_p->dip), - ddi_get_instance(ctrl_p->dip))); - } -} - -static void -pciehpc_enable_errors(pciehpc_t *ctrl_p) -{ - if (ctrl_p->soft_state & PCIEHPC_SOFT_STATE_PCIE_DEV) { - (void) PCIE_ENABLE_ERRORS(ctrl_p->dip); - PCIEHPC_DEBUG3((CE_NOTE, "%s%d: pciehpc_enable_errors\n", - ddi_driver_name(ctrl_p->dip), - ddi_get_instance(ctrl_p->dip))); - } -} diff --git a/usr/src/uts/common/io/hotplug/pcihp/pcihp.c b/usr/src/uts/common/io/hotplug/pcihp/pcihp.c index 0faa5b8bdf..d84b72c7cb 100644 --- a/usr/src/uts/common/io/hotplug/pcihp/pcihp.c +++ b/usr/src/uts/common/io/hotplug/pcihp/pcihp.c @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -258,9 +258,6 @@ static int pcihp_get_board_type(struct pcihp_slotinfo *); /* sysevent function */ static void pcihp_gen_sysevent(char *, int, int, dev_info_t *, int); -extern int pcicfg_configure(dev_info_t *, uint_t); -extern int pcicfg_unconfigure(dev_info_t *, uint_t); - static int pcihp_list_occupants(dev_info_t *, void *); static int pcihp_indirect_map(dev_info_t *dip); @@ -1558,7 +1555,8 @@ pcihp_configure_ap(pcihp_t *pcihp_p, int pci_dev) /* * Call the configurator to configure the card. */ - if (pcicfg_configure(self, pci_dev) != PCICFG_SUCCESS) { + if (pcicfg_configure(self, pci_dev, PCICFG_ALL_FUNC, 0) + != PCICFG_SUCCESS) { if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, @@ -1717,8 +1715,8 @@ pcihp_unconfigure_ap(pcihp_t *pcihp_p, int pci_dev) (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIG_START, NULL); - if (pcicfg_unconfigure(self, - pci_dev) == PCICFG_SUCCESS) { + if (pcicfg_unconfigure(self, pci_dev, + PCICFG_ALL_FUNC, 0) == PCICFG_SUCCESS) { /* * Now that resources are freed, * clear EXT and Turn LED ON. @@ -2155,7 +2153,8 @@ pcihp_new_slot_state(dev_info_t *dip, hpc_slot_t hdl, /* * Call the configurator to configure the card. */ - if (pcicfg_configure(dip, pci_dev) != PCICFG_SUCCESS) { + if (pcicfg_configure(dip, pci_dev, PCICFG_ALL_FUNC, 0) + != PCICFG_SUCCESS) { if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, @@ -2465,7 +2464,8 @@ pcihp_event_handler(caddr_t slot_arg, uint_t event_mask) /* * Call the configurator to configure the card. */ - if (pcicfg_configure(pcihp_p->dip, pci_dev) != PCICFG_SUCCESS) { + if (pcicfg_configure(pcihp_p->dip, pci_dev, PCICFG_ALL_FUNC, 0) + != PCICFG_SUCCESS) { if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, @@ -2611,8 +2611,8 @@ pcihp_event_handler(caddr_t slot_arg, uint_t event_mask) (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIG_START, NULL); - if (pcicfg_unconfigure(pcihp_p->dip, - pci_dev) == PCICFG_SUCCESS) { + if (pcicfg_unconfigure(pcihp_p->dip, pci_dev, + PCICFG_ALL_FUNC, 0) == PCICFG_SUCCESS) { /* Resources freed. Turn LED on. Clear EXT. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { diff --git a/usr/src/uts/common/io/hotplug/pcishpc/pcishpc.c b/usr/src/uts/common/io/hotplug/pcishpc/pcishpc.c deleted file mode 100644 index 0ceac54b34..0000000000 --- a/usr/src/uts/common/io/hotplug/pcishpc/pcishpc.c +++ /dev/null @@ -1,2656 +0,0 @@ -/* - * 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 2008 Sun Microsystems, Inc. All rights reserved. - * Use is subject to license terms. - */ - -/* - * PCISHPC - The Standard PCI HotPlug Controller driver module. This driver - * can be used with PCI HotPlug controllers that are compatible - * with the PCI SHPC specification 1.x. - */ - -#include <sys/note.h> -#include <sys/conf.h> -#include <sys/kmem.h> -#include <sys/kstat.h> -#include <sys/debug.h> -#include <sys/vtrace.h> -#include <sys/modctl.h> -#include <sys/autoconf.h> -#include <sys/varargs.h> -#include <sys/hwconf.h> -#include <sys/ddi_impldefs.h> -#include <sys/pci.h> -#include <sys/callb.h> -#include <sys/ddi.h> -#include <sys/sunddi.h> -#include <sys/sunndi.h> -#include <sys/ndi_impldefs.h> -#include <sys/hotplug/pci/pcishpc.h> -#include <sys/hotplug/pci/pcishpc_regs.h> -#include <sys/hotplug/hpcsvc.h> - - -/* General Register bit weights for the 32-bit SHPC registers */ -#define REG_BIT0 0x00000001 -#define REG_BIT1 0x00000002 -#define REG_BIT2 0x00000004 -#define REG_BIT3 0x00000008 -#define REG_BIT4 0x00000010 -#define REG_BIT5 0x00000020 -#define REG_BIT6 0x00000040 -#define REG_BIT7 0x00000080 -#define REG_BIT8 0x00000100 -#define REG_BIT9 0x00000200 -#define REG_BIT10 0x00000400 -#define REG_BIT11 0x00000800 -#define REG_BIT12 0x00001000 -#define REG_BIT13 0x00002000 -#define REG_BIT14 0x00004000 -#define REG_BIT15 0x00008000 -#define REG_BIT16 0x00010000 -#define REG_BIT17 0x00020000 -#define REG_BIT18 0x00040000 -#define REG_BIT19 0x00080000 -#define REG_BIT20 0x00100000 -#define REG_BIT21 0x00200000 -#define REG_BIT22 0x00400000 -#define REG_BIT23 0x00800000 -#define REG_BIT24 0x01000000 -#define REG_BIT25 0x02000000 -#define REG_BIT26 0x04000000 -#define REG_BIT27 0x08000000 -#define REG_BIT28 0x10000000 -#define REG_BIT29 0x20000000 -#define REG_BIT30 0x40000000 -#define REG_BIT31 0x80000000 - -/* Definitions used with the SHPC SHPC_SLOTS_AVAIL_I_REG register */ -#define SHPC_AVAIL_33MHZ_CONV_SPEED_SHIFT 0 -#define SHPC_AVAIL_66MHZ_PCIX_SPEED_SHIFT 8 -#define SHPC_AVAIL_100MHZ_PCIX_SPEED_SHIFT 16 -#define SHPC_AVAIL_133MHZ_PCIX_SPEED_SHIFT 24 -#define SHPC_AVAIL_SPEED_MASK 0x1F - -/* Definitions used with the SHPC SHPC_SLOTS_AVAIL_II_REG register */ -#define SHPC_AVAIL_66MHZ_CONV_SPEED_SHIFT 0 - -/* Register bits used with the SHPC SHPC_PROF_IF_SBCR_REG register */ -#define SHPC_SBCR_33MHZ_CONV_SPEED 0 -#define SHPC_SBCR_66MHZ_CONV_SPEED REG_BIT0 -#define SHPC_SBCR_66MHZ_PCIX_SPEED REG_BIT1 -#define SHPC_SBCR_100MHZ_PCIX_SPEED (REG_BIT0|REG_BIT1) -#define SHPC_SBCR_133MHZ_PCIX_SPEED REG_BIT2 -#define SHPC_SBCR_SPEED_MASK (REG_BIT0|REG_BIT1|REG_BIT2) - -/* Register bits used with the SHPC SHPC_COMMAND_STATUS_REG register */ -#define SHPC_COMM_STS_ERR_INVALID_SPEED REG_BIT19 -#define SHPC_COMM_STS_ERR_INVALID_COMMAND REG_BIT18 -#define SHPC_COMM_STS_ERR_MRL_OPEN REG_BIT17 -#define SHPC_COMM_STS_ERR_MASK (REG_BIT17|REG_BIT18|REG_BIT19) -#define SHPC_COMM_STS_CTRL_BUSY REG_BIT16 -#define SHPC_COMM_STS_SET_SPEED REG_BIT6 - -/* Register bits used with the SHPC SHPC_CTRL_SERR_INT_REG register */ -#define SHPC_SERR_INT_GLOBAL_IRQ_MASK REG_BIT0 -#define SHPC_SERR_INT_GLOBAL_SERR_MASK REG_BIT1 -#define SHPC_SERR_INT_CMD_COMPLETE_MASK REG_BIT2 -#define SHPC_SERR_INT_ARBITER_SERR_MASK REG_BIT3 -#define SHPC_SERR_INT_CMD_COMPLETE_IRQ REG_BIT16 -#define SHPC_SERR_INT_ARBITER_IRQ REG_BIT17 -#define SHPC_SERR_INT_MASK_ALL (REG_BIT0|REG_BIT1|REG_BIT2|REG_BIT3) - -/* Register bits used with the SHPC SHPC_LOGICAL_SLOT_REGS register */ -#define SHPC_SLOT_POWER_ONLY REG_BIT0 -#define SHPC_SLOT_ENABLED REG_BIT1 -#define SHPC_SLOT_DISABLED (REG_BIT0 | REG_BIT1) -#define SHPC_SLOT_STATE_MASK (REG_BIT0 | REG_BIT1) -#define SHPC_SLOT_MRL_STATE_MASK REG_BIT8 -#define SHPC_SLOT_66MHZ_CONV_CAPABLE REG_BIT9 -#define SHPC_SLOT_CARD_EMPTY_MASK (REG_BIT10 | REG_BIT11) -#define SHPC_SLOT_66MHZ_PCIX_CAPABLE REG_BIT12 -#define SHPC_SLOT_100MHZ_PCIX_CAPABLE REG_BIT13 -#define SHPC_SLOT_133MHZ_PCIX_CAPABLE (REG_BIT12 | REG_BIT13) -#define SHPC_SLOT_PCIX_CAPABLE_MASK (REG_BIT12 | REG_BIT13) -#define SHPC_SLOT_PCIX_CAPABLE_SHIFT 12 -#define SHPC_SLOT_PRESENCE_DETECTED REG_BIT16 -#define SHPC_SLOT_ISO_PWR_DETECTED REG_BIT17 -#define SHPC_SLOT_ATTN_DETECTED REG_BIT18 -#define SHPC_SLOT_MRL_DETECTED REG_BIT19 -#define SHPC_SLOT_POWER_DETECTED REG_BIT20 -#define SHPC_SLOT_PRESENCE_MASK REG_BIT24 -#define SHPC_SLOT_ISO_PWR_MASK REG_BIT25 -#define SHPC_SLOT_ATTN_MASK REG_BIT26 -#define SHPC_SLOT_MRL_MASK REG_BIT27 -#define SHPC_SLOT_POWER_MASK REG_BIT28 -#define SHPC_SLOT_MRL_SERR_MASK REG_BIT29 -#define SHPC_SLOT_POWER_SERR_MASK REG_BIT30 -#define SHPC_SLOT_MASK_ALL (REG_BIT24|REG_BIT25|REG_BIT26|\ - REG_BIT27|REG_BIT28|REG_BIT30) - -/* Register bits used with the SHPC SHPC_IRQ_LOCATOR_REG register. */ -#define SHPC_IRQ_CMD_COMPLETE REG_BIT0 -#define SHPC_IRQ_SLOT_N_PENDING REG_BIT1 - -/* Register bits used with the SHPC SHPC_SERR_LOCATOR_REG register. */ -#define SHPC_IRQ_SERR_ARBITER_PENDING REG_BIT0 -#define SHPC_IRQ_SERR_SLOT_N_PENDING REG_BIT1 - -/* Register bits used with the SHPC SHPC_SLOT_CONFIGURATION_REG register */ -#define SHPC_SLOT_CONFIG_MRL_SENSOR REG_BIT30 -#define SHPC_SLOT_CONFIG_ATTN_BUTTON REG_BIT31 -#define SHPC_SLOT_CONFIG_PHY_SLOT_NUM_SHIFT 16 -#define SHPC_SLOT_CONFIG_PHY_SLOT_NUM_MASK 0x3FF -#define SHPC_SLOT_CONFIG_PHY_SLOT_NUM(reg) (((reg) >> 16) & 0x3FF) - -/* Max PCISHPC controller slots */ -#define MAX_SHPC_SLOTS 31 - -/* PCISHPC controller command complete delay in microseconds. */ -#define SHPC_COMMAND_WAIT_TIME 10000 - -/* - * Power good wait time after issuing a command to change the slot state - * to power only state. - */ -#define SHPC_POWER_GOOD_WAIT_TIME 220000 - -/* reset delay to 1 sec. */ -static int pcishpc_reset_delay = 1000000; - -/* PCISHPC controller softstate structure */ -typedef struct pcishpc_ctrl { - dev_info_t *shpc_dip; /* DIP for SHPC Nexus */ - ddi_acc_handle_t shpc_config_hdl; /* SHPC DDI cfg handle */ - kmutex_t shpc_intr_mutex; /* Interrupt mutex lock */ - boolean_t interrupt_installed; /* Interrupt installed */ - boolean_t command_complete; /* Got a cmd complete IRQ */ - kcondvar_t cmd_comp_cv; - boolean_t arbiter_timeout; /* Got a Arb timeout IRQ */ - kmutex_t shpc_mutex; /* Mutex for this SHPC */ - char nexus_path[MAXNAMELEN]; /* Pathname of Nexus */ - uint32_t shpc_bus; /* SHPC bus */ - uint32_t shpc_dev; /* SHPC device */ - uint32_t shpc_func; /* SHPC function */ - uint8_t shpc_dword_select; /* SHPC register offset */ - uint8_t shpc_dword_data_reg; /* SHPC data register */ - uint32_t shpc_slots_avail1_reg; /* SHPC Slots Available1 Reg */ - uint32_t shpc_slots_avail2_reg; /* SHPC Slots Available2 Reg */ - uint32_t numSlotsImpl; /* # of HP Slots Implemented */ - uint32_t numSlotsConn; /* # of HP Slots Connected */ - int currBusSpeed; /* Current Bus Speed */ - uint32_t deviceStart; /* 1st PCI Device # */ - uint32_t physStart; /* 1st Phys Device # */ - uint32_t deviceIncreases; /* Device # Increases */ - struct pcishpc *slots[MAX_SHPC_SLOTS]; /* Slot pointers */ - boolean_t has_attn; /* Do we have attn btn? */ - boolean_t has_mrl; /* Do we have MRL? */ - struct pcishpc_ctrl *nextp; /* Linked list pointer */ -} pcishpc_ctrl_t; - -/* PCISHPC slot softstate structure */ -typedef struct pcishpc { - pcishpc_ctrl_t *ctrl; /* SHPC ctrl for this slot */ - hpc_slot_info_t slot_info; /* HPS framework slot info */ - hpc_slot_t slot_handle; /* HPS framework handle */ - hpc_slot_ops_t *slot_ops; /* HPS framework callbacks */ - uint32_t fault_led_state; /* Fault LED state */ - uint32_t power_led_state; /* Power LED state */ - uint32_t attn_led_state; /* Attn LED state */ - uint32_t active_led_state; /* Active LED state */ - hpc_slot_state_t slot_state; /* Slot State */ - uint32_t deviceNum; /* PCI device num for slot */ - uint32_t slotNum; /* SHPC slot number */ - uint32_t phy_slot_num; /* physical slot number */ - uint32_t slot_events; /* Slot event(s) IRQ */ - kcondvar_t attn_btn_cv; /* ATTN button pressed intr */ - boolean_t attn_btn_pending; - kthread_t *attn_btn_threadp; /* ATTN button event thread */ - boolean_t attn_btn_thread_exit; - struct pcishpc *nextp; /* Linked list pointer */ -} pcishpc_t; -/* mutex to protect the shpc_head and shpc_ctrl_head linked lists */ -static kmutex_t pcishpc_list_mutex; - -/* Pointer to a linked list of shpc slot softstate structures */ -static pcishpc_t *pcishpc_head = NULL; - -/* Pointer to a linked list of shpc controller softstate structures */ -static pcishpc_ctrl_t *pcishpc_ctrl_head = NULL; - -/* mutex to protect access to the controller */ -static kmutex_t pcishpc_control_mutex; - -/* SHPC static function prototypes */ -static pcishpc_ctrl_t *pcishpc_create_controller(dev_info_t *dip); -static int pcishpc_destroy_controller(dev_info_t *dip); -static pcishpc_ctrl_t *pcishpc_get_controller(dev_info_t *dip); -static pcishpc_t *pcishpc_create_slot(pcishpc_ctrl_t *ctrl_p); -static int pcishpc_destroy_slots(pcishpc_ctrl_t *ctrl_p); -static pcishpc_t *pcishpc_hpc_get_slot_state(hpc_slot_t slot); -static int pcishpc_setup_controller(pcishpc_ctrl_t *ctrl_p); -static int pcishpc_register_slot(pcishpc_ctrl_t *ctrl_p, int slot); -static int pcishpc_connect(caddr_t ops_arg, - hpc_slot_t slot_hdl, void *data, - uint_t flags); -static int pcishpc_disconnect(caddr_t ops_arg, - hpc_slot_t slot_hdl, void *data, - uint_t flags); -static int pcishpc_pci_control(caddr_t ops_arg, hpc_slot_t slot_hdl, - int request, caddr_t arg); -static int pcishpc_setled(pcishpc_t *pcishpc_p, hpc_led_t led, - hpc_led_state_t state); -static int pcishpc_set_power_state(pcishpc_t *pcishpc_p, - hpc_slot_state_t state); -static int pcishpc_set_bus_speed(pcishpc_t *pcishpc_p); -static int pcishpc_probe_controller(pcishpc_ctrl_t *pcishpc_p); -static int pcishpc_get_pci_info(pcishpc_ctrl_t *pcishpc_p); -static void pcishpc_get_slot_state(pcishpc_t *pcishpc_p); -static int pcishpc_process_intr(pcishpc_ctrl_t *ctrl_p); -static int pcishpc_enable_irqs(pcishpc_ctrl_t *ctrl_p); -static int pcishpc_disable_irqs(pcishpc_ctrl_t *ctrl_p); -static void pcishpc_set_soft_int(pcishpc_ctrl_t *ctrl_p); -static int pcishpc_wait_busy(pcishpc_ctrl_t *ctrl_p); -static int pcishpc_issue_command(pcishpc_ctrl_t *ctrl_p, - uint32_t cmd_code); -static int pcishpc_led_shpc_to_hpc(int state); -static int pcishpc_led_hpc_to_shpc(int state); -static int pcishpc_slot_shpc_to_hpc(int state); -static int pcishpc_slot_hpc_to_shpc(int state); -static char *pcishpc_textledstate(hpc_led_state_t state); -static char *pcishpc_textslotstate(hpc_slot_state_t state); -static char *pcishpc_textrequest(int request); -static int pcishpc_set_slot_state(pcishpc_t *pcishpc_p); -static void pcishpc_dump_regs(pcishpc_ctrl_t *ctrl_p); -static void pcishpc_write_reg(pcishpc_ctrl_t *ctrl_p, int reg, - uint32_t data); -static uint32_t pcishpc_read_reg(pcishpc_ctrl_t *ctrl_p, int reg); -static void pcishpc_debug(char *fmt, ...); - -static void pcishpc_attn_btn_handler(pcishpc_t *pcishpc_p); -static void pcishpc_set_slot_name(pcishpc_ctrl_t *ctrl_p, int slot); - -static int pcishpc_debug_enabled = 0; - -/* Module operations information for the kernel */ -extern struct mod_ops mod_miscops; -static struct modlmisc modlmisc = { - &mod_miscops, - "PCI SHPC hotplug module", -}; - -/* Module linkage information for the kernel */ -static struct modlinkage modlinkage = { - MODREV_1, - &modlmisc, - NULL -}; - -int -_init(void) -{ - int rc; - - if ((rc = mod_install(&modlinkage)) != 0) { - pcishpc_debug("pcishpc: install error=%d", rc); - return (rc); - } - - /* Init the shpc driver list mutex. */ - mutex_init(&pcishpc_list_mutex, NULL, MUTEX_DRIVER, NULL); - /* Init the shpc control mutex. */ - mutex_init(&pcishpc_control_mutex, NULL, MUTEX_DRIVER, NULL); - - pcishpc_debug("pcishpc: installed"); - return (rc); -} - -int -_fini(void) -{ - pcishpc_debug("pcishpc: _fini called()"); - /* XXX - to be fixed later */ - return (EBUSY); -} - -int -_info(struct modinfo *modinfop) -{ - pcishpc_debug("pcishpc: _info called()"); - return (mod_info(&modlinkage, modinfop)); -} - - -/* - * pcishpc_create_controller() - * - * This function allocates and creates an SHPC controller state structure - * and adds it to the linked list of controllers. - */ -static pcishpc_ctrl_t * -pcishpc_create_controller(dev_info_t *dip) -{ - pcishpc_ctrl_t *ctrl_p; - - pcishpc_debug("pcishpc: create controller for %s#%d", - ddi_driver_name(dip), ddi_get_instance(dip)); - - ctrl_p = kmem_zalloc(sizeof (pcishpc_ctrl_t), KM_SLEEP); - - ctrl_p->interrupt_installed = B_FALSE; - ctrl_p->shpc_dip = dip; - - (void) ddi_pathname(dip, ctrl_p->nexus_path); - - /* Get the PCI BUS,DEVICE,FUNCTION for this SHPC controller. */ - if (pcishpc_get_pci_info(ctrl_p) != DDI_SUCCESS) { - - pcishpc_debug("pcishpc_create_controller() " - "Error: pcishpc_get_pci_info() failed"); - kmem_free(ctrl_p, sizeof (pcishpc_ctrl_t)); - return (NULL); - } - - if (pci_config_setup(dip, &ctrl_p->shpc_config_hdl) != DDI_SUCCESS) { - pcishpc_debug("pcishpc_create_controller() " - "Error: Unable to map SHPC PCI Config registers"); - kmem_free(ctrl_p, sizeof (pcishpc_ctrl_t)); - return (NULL); - } - - /* Make sure the SHPC is listed in the PCI capibilities list. */ - if (pcishpc_probe_controller(ctrl_p) != DDI_SUCCESS) { - pcishpc_debug("pcishpc_create_controller() " - "Error: Unable to find SHPC controller"); - pci_config_teardown(&ctrl_p->shpc_config_hdl); - kmem_free(ctrl_p, sizeof (pcishpc_ctrl_t)); - return (NULL); - } - - /* Init the interrupt mutex */ - mutex_init(&ctrl_p->shpc_intr_mutex, NULL, MUTEX_DRIVER, - (void *)PCISHPC_INTR_PRI); - - /* Interrupts are now enabled. */ - ctrl_p->interrupt_installed = B_TRUE; - - /* Init the shpc controller's mutex. */ - mutex_init(&ctrl_p->shpc_mutex, NULL, MUTEX_DRIVER, NULL); - - mutex_enter(&pcishpc_list_mutex); - - /* Insert new softstate into linked list of current soft states. */ - ctrl_p->nextp = pcishpc_ctrl_head; - pcishpc_ctrl_head = ctrl_p; - - mutex_exit(&pcishpc_list_mutex); - - pcishpc_debug("pcishpc_create_controller() success"); - - return (ctrl_p); -} - - -/* - * pcishpc_probe_controller() - * - * This function probes to make sure there is indeed an SHPC controller. - */ -static int -pcishpc_probe_controller(pcishpc_ctrl_t *ctrl_p) -{ - uint8_t cap_ptr; - uint8_t cap_id; - uint16_t status; - - status = pci_config_get16(ctrl_p->shpc_config_hdl, PCI_CONF_STAT); - if (!(status & PCI_STAT_CAP)) { - return (DDI_FAILURE); - } - - /* Get a pointer to the PCI capabilities list. */ - cap_ptr = pci_config_get8(ctrl_p->shpc_config_hdl, PCI_BCNF_CAP_PTR); - - cap_ptr &= 0xFC; - - /* Walk PCI capabilities list searching for the SHPC capability. */ - while (cap_ptr != PCI_CAP_NEXT_PTR_NULL) { - cap_id = pci_config_get8(ctrl_p->shpc_config_hdl, cap_ptr); - - pcishpc_debug("pcishpc_probe_controller() capability @ " - "pointer=%02x (id=%02x)", cap_ptr, cap_id); - - if (cap_id == PCI_CAP_ID_PCI_HOTPLUG) { - /* Save the SHPC register offset. */ - ctrl_p->shpc_dword_select = cap_ptr+2; - /* Save the SHPC data register. */ - ctrl_p->shpc_dword_data_reg = cap_ptr+4; - break; - } - - /* Get the pointer to the next capability. */ - cap_ptr = pci_config_get8(ctrl_p->shpc_config_hdl, - cap_ptr+1); - - cap_ptr &= 0xFC; - } - - if (cap_ptr == PCI_CAP_NEXT_PTR_NULL) { - return (DDI_FAILURE); - } - - pcishpc_debug("pcishpc_probe_controller() Found SHPC capibility"); - - return (DDI_SUCCESS); -} - - -/* - * pcishpc_destroy_controller() - * - * This function deallocates all of the SHPC controller resources. - */ -static int -pcishpc_destroy_controller(dev_info_t *dip) -{ - pcishpc_ctrl_t *ctrl_p; - pcishpc_ctrl_t **ctrl_pp; - - pcishpc_debug("pcishpc_destroy_controller() called(dip=%p)", dip); - - mutex_enter(&pcishpc_list_mutex); - - ctrl_pp = &pcishpc_ctrl_head; - - /* Walk the linked list of softstates. */ - while ((ctrl_p = *ctrl_pp) != NULL) { - if (ctrl_p->shpc_dip == dip) { - /* - * Deallocate the slot state structures for - * this controller. - */ - (void) pcishpc_destroy_slots(ctrl_p); - - *ctrl_pp = ctrl_p->nextp; - - pci_config_teardown(&ctrl_p->shpc_config_hdl); - - cv_destroy(&ctrl_p->cmd_comp_cv); - - mutex_destroy(&ctrl_p->shpc_mutex); - mutex_destroy(&ctrl_p->shpc_intr_mutex); - kmem_free(ctrl_p, sizeof (pcishpc_ctrl_t)); - mutex_exit(&pcishpc_list_mutex); - - pcishpc_debug("pcishpc_destroy_controller() success"); - return (DDI_SUCCESS); - } - ctrl_pp = &(ctrl_p->nextp); - } - - mutex_exit(&pcishpc_list_mutex); - - pcishpc_debug("pcishpc_destroy_controller() not found"); - - return (DDI_FAILURE); -} - - -/* - * pcishpc_intr() - * - * This is the SHPC controller interrupt handler. - */ -int -pcishpc_intr(dev_info_t *dip) -{ - pcishpc_ctrl_t *ctrl_p = pcishpc_get_controller(dip); - int slot; - uint32_t irq_locator, irq_serr_locator, reg; - boolean_t slot_event = B_FALSE; - - pcishpc_debug("pcishpc_intr() called"); - - if (ctrl_p->interrupt_installed == B_TRUE) { - mutex_enter(&ctrl_p->shpc_intr_mutex); - - pcishpc_debug("pcishpc_intr() interrupt received"); - - reg = pcishpc_read_reg(ctrl_p, SHPC_CTRL_SERR_INT_REG); - - if (reg & SHPC_SERR_INT_CMD_COMPLETE_IRQ) { - pcishpc_debug("pcishpc_intr() " - "SHPC_SERR_INT_CMD_COMPLETE_IRQ detected"); - ctrl_p->command_complete = B_TRUE; - cv_signal(&ctrl_p->cmd_comp_cv); - } - - if (reg & SHPC_SERR_INT_ARBITER_IRQ) { - pcishpc_debug("pcishpc_intr() SHPC_SERR_INT_ARBITER_IRQ" - " detected"); - ctrl_p->arbiter_timeout = B_TRUE; - } - - /* Write back the SERR INT register to acknowledge the IRQs. */ - pcishpc_write_reg(ctrl_p, SHPC_CTRL_SERR_INT_REG, reg); - - irq_locator = pcishpc_read_reg(ctrl_p, SHPC_IRQ_LOCATOR_REG); - - irq_serr_locator = pcishpc_read_reg(ctrl_p, - SHPC_SERR_LOCATOR_REG); - - /* Check for slot events that might have occured. */ - for (slot = 0; slot < ctrl_p->numSlotsImpl; slot++) { - if ((irq_locator & (SHPC_IRQ_SLOT_N_PENDING<<slot)) || - (irq_serr_locator & - (SHPC_IRQ_SERR_SLOT_N_PENDING<<slot))) { - pcishpc_debug("pcishpc_intr() slot %d and " - "pending IRQ", slot+1); - - /* - * Note that we will need to generate a - * slot event interrupt. - */ - slot_event = B_TRUE; - - reg = pcishpc_read_reg(ctrl_p, - SHPC_LOGICAL_SLOT_REGS+slot); - - /* Record any pending slot interrupts/events. */ - ctrl_p->slots[slot]->slot_events |= reg; - - /* Acknoledge any slot interrupts */ - pcishpc_write_reg(ctrl_p, - SHPC_LOGICAL_SLOT_REGS+slot, reg); - } - } - - if (slot_event == B_TRUE) { - pcishpc_debug("pcishpc_intr() slot(s) have event(s)"); - (void) pcishpc_process_intr(ctrl_p); - } else { - pcishpc_debug("pcishpc_intr() No slot event(s)"); - } - - mutex_exit(&ctrl_p->shpc_intr_mutex); - - pcishpc_debug("pcishpc_intr() claimed"); - - return (DDI_INTR_CLAIMED); - } - - pcishpc_debug("pcishpc_intr() unclaimed"); - - return (DDI_INTR_UNCLAIMED); -} - -/* - * pcishpc_process_intr() - * - * This is the SHPC soft interrupt handler. - */ -static int -pcishpc_process_intr(pcishpc_ctrl_t *ctrl_p) -{ - int slot; - - mutex_enter(&ctrl_p->shpc_mutex); - - pcishpc_debug("pcishpc_process_intr() called"); - - /* XXX - add event handling code here */ - for (slot = 0; slot < ctrl_p->numSlotsImpl; slot++) { - if (ctrl_p->slots[slot]->slot_events & - SHPC_SLOT_PRESENCE_DETECTED) - pcishpc_debug("slot %d: SHPC_SLOT_PRESENCE_DETECTED", - slot+1); - - if (ctrl_p->slots[slot]->slot_events & - SHPC_SLOT_ISO_PWR_DETECTED) - pcishpc_debug("slot %d: SHPC_SLOT_ISO_PWR_DETECTED", - slot+1); - - if (ctrl_p->slots[slot]->slot_events & - SHPC_SLOT_ATTN_DETECTED) { - pcishpc_debug("slot %d: SHPC_SLOT_ATTN_DETECTED", - slot+1); - /* - * if ATTN button event is still pending - * then cancel it - */ - if (ctrl_p->slots[slot]->attn_btn_pending == B_TRUE) - ctrl_p->slots[slot]->attn_btn_pending = B_FALSE; - - /* wake up the ATTN event handler */ - cv_signal(&ctrl_p->slots[slot]->attn_btn_cv); - } - - if (ctrl_p->slots[slot]->slot_events & SHPC_SLOT_MRL_DETECTED) - pcishpc_debug("slot %d: SHPC_SLOT_MRL_DETECTED", - slot+1); - - if (ctrl_p->slots[slot]->slot_events & SHPC_SLOT_POWER_DETECTED) - pcishpc_debug("slot %d: SHPC_SLOT_POWER_DETECTED", - slot+1); - - /* Clear the events now that we've processed all of them. */ - ctrl_p->slots[slot]->slot_events = 0; - } - - mutex_exit(&ctrl_p->shpc_mutex); - - return (DDI_INTR_CLAIMED); -} - - -/* - * pcishpc_get_controller() - * - * This function retrieves the hot plug SHPC controller soft state. - */ -static pcishpc_ctrl_t * -pcishpc_get_controller(dev_info_t *dip) -{ - pcishpc_ctrl_t *ctrl_p; - - pcishpc_debug("pcishpc_get_controller() called (dip=%p)", dip); - - mutex_enter(&pcishpc_list_mutex); - - ctrl_p = pcishpc_ctrl_head; - - while (ctrl_p) { - if (ctrl_p->shpc_dip == dip) - break; - ctrl_p = ctrl_p->nextp; - } - - mutex_exit(&pcishpc_list_mutex); - - pcishpc_debug("pcishpc_get_controller() (ctrl_p=%llx)", ctrl_p); - - return (ctrl_p); -} - - -/* - * pcishpc_hpc_get_slot_state() - * - * This function retrieves the hot plug SHPC soft state from the - * the HPS framework slot handle. - */ -static pcishpc_t * -pcishpc_hpc_get_slot_state(hpc_slot_t slot) -{ - pcishpc_t *pcishpc_p; - - pcishpc_debug("pcishpc_hpc_get_slot_state() called (hpc_slot=%x)", - slot); - - mutex_enter(&pcishpc_list_mutex); - - pcishpc_p = pcishpc_head; - - while (pcishpc_p) { - if (pcishpc_p->slot_handle == slot) { - pcishpc_debug("pcishpc_hpc_get_slot_state() found " - "(pcishpc=%x)", pcishpc_p); - mutex_exit(&pcishpc_list_mutex); - return (pcishpc_p); - } - pcishpc_p = pcishpc_p->nextp; - } - - mutex_exit(&pcishpc_list_mutex); - - pcishpc_debug("pcishpc_hpc_get_slot_state() failed (slot=%x)", slot); - - return (NULL); -} - - -/* - * pcishpc_get_pci_info() - * - * Read the PCI Bus, PCI Device, and PCI function for the SHPC controller. - */ -static int -pcishpc_get_pci_info(pcishpc_ctrl_t *pcishpc_p) -{ - pci_regspec_t *regspec; - int reglen; - - pcishpc_debug("pcishpc_get_pci_info() called"); - - if (ddi_getlongprop(DDI_DEV_T_NONE, pcishpc_p->shpc_dip, - DDI_PROP_DONTPASS, "reg", (caddr_t)®spec, ®len) - != DDI_SUCCESS) { - pcishpc_debug("pcishpc_get_pci_info() failed to get regspec."); - return (DDI_FAILURE); - } - - pcishpc_p->shpc_bus = PCI_REG_BUS_G(regspec[0].pci_phys_hi); - pcishpc_p->shpc_dev = PCI_REG_DEV_G(regspec[0].pci_phys_hi); - pcishpc_p->shpc_func = PCI_REG_FUNC_G(regspec[0].pci_phys_hi); - - kmem_free(regspec, reglen); - - pcishpc_debug("pcishpc_get_pci_info() %s%d: bus=%d, dev=%d, func=%d", - ddi_driver_name(pcishpc_p->shpc_dip), - ddi_get_instance(pcishpc_p->shpc_dip), - pcishpc_p->shpc_bus, pcishpc_p->shpc_dev, - pcishpc_p->shpc_func); - - return (DDI_SUCCESS); -} - - -/* - * pcishpc_init() - * - * Install and configure an SHPC controller and register the HotPlug slots - * with the Solaris HotPlug framework. This function is usually called by - * a PCI bridge Nexus driver that has a built in SHPC controller. - */ -int -pcishpc_init(dev_info_t *dip) -{ - pcishpc_ctrl_t *ctrl_p; - int i; - - pcishpc_debug("pcishpc_init() called from %s#%d", - ddi_driver_name(dip), ddi_get_instance(dip)); - - mutex_enter(&pcishpc_control_mutex); - - if (pcishpc_get_controller(dip) != NULL) { - pcishpc_debug("pcishpc_init() shpc instance already " - "initialized!"); - mutex_exit(&pcishpc_control_mutex); - return (DDI_SUCCESS); - } - - /* Initialize soft state structure for the SHPC instance. */ - ctrl_p = pcishpc_create_controller(dip); - - if (ctrl_p == NULL) { - pcishpc_debug("pcishpc_init() failed to create shpc softstate"); - mutex_exit(&pcishpc_control_mutex); - return (DDI_FAILURE); - } - - if (pcishpc_setup_controller(ctrl_p) != DDI_SUCCESS) { - pcishpc_debug("pcishpc_init() failed to setup controller"); - (void) pcishpc_destroy_controller(dip); - mutex_exit(&pcishpc_control_mutex); - return (DDI_FAILURE); - } - -#if 0 - pcishpc_debug("%s%d: P2P bridge register dump:", - ddi_driver_name(dip), ddi_get_instance(dip)); - - for (i = 0; i < 0x100; i += 4) { - pcishpc_debug("SHPC Cfg reg 0x%02x: %08x", i, - pci_config_get32(ctrl_p->shpc_config_hdl, i)); - } -#endif - - /* Setup each HotPlug slot on this SHPC controller. */ - for (i = 0; i < ctrl_p->numSlotsImpl; i++) { - if (pcishpc_register_slot(ctrl_p, i) != DDI_SUCCESS) { - pcishpc_debug("pcishpc_init() failed to register " - "slot %d", i); - (void) pcishpc_destroy_controller(dip); - mutex_exit(&pcishpc_control_mutex); - return (DDI_FAILURE); - } - } - - (void) pcishpc_enable_irqs(ctrl_p); - - if (pcishpc_debug_enabled) { - /* Dump out the SHPC registers. */ - pcishpc_dump_regs(ctrl_p); - } - - mutex_exit(&pcishpc_control_mutex); - - pcishpc_debug("pcishpc_init() success(dip=%p)", dip); - return (DDI_SUCCESS); -} - - -/* - * pcishpc_enable_irqs() - * - * Enable/unmask the different IRQ's we support from the SHPC controller. - */ -static int -pcishpc_enable_irqs(pcishpc_ctrl_t *ctrl_p) -{ - uint32_t reg; - int slot; - - reg = pcishpc_read_reg(ctrl_p, SHPC_CTRL_SERR_INT_REG); - - /* Enable all interrupts. */ - reg &= ~SHPC_SERR_INT_MASK_ALL; - - pcishpc_write_reg(ctrl_p, SHPC_CTRL_SERR_INT_REG, reg); - - /* Unmask the interrupts for each slot. */ - for (slot = 0; slot < ctrl_p->numSlotsImpl; slot++) { - ctrl_p->slots[slot]->slot_events = 0; - - reg = pcishpc_read_reg(ctrl_p, SHPC_LOGICAL_SLOT_REGS+slot); - if ((reg & SHPC_SLOT_STATE_MASK) == SHPC_SLOT_ENABLED) { - reg &= ~(SHPC_SLOT_MASK_ALL | SHPC_SLOT_MRL_SERR_MASK); - ctrl_p->numSlotsConn++; - if (ctrl_p->currBusSpeed == -1) - ctrl_p->currBusSpeed = pcishpc_read_reg(ctrl_p, - SHPC_PROF_IF_SBCR_REG) & - SHPC_SBCR_SPEED_MASK; - } else { - reg &= ~(SHPC_SLOT_MASK_ALL); - } - - /* Enable/Unmask all slot interrupts. */ - pcishpc_write_reg(ctrl_p, SHPC_LOGICAL_SLOT_REGS+slot, reg); - } - - pcishpc_debug("pcishpc_enable_irqs: ctrl_p 0x%p, " - "current bus speed 0x%x, slots connected 0x%x\n", ctrl_p, - ctrl_p->currBusSpeed, ctrl_p->numSlotsConn); - - return (DDI_SUCCESS); -} - - -/* - * pcishpc_disable_irqs() - * - * Disable/Mask the different IRQ's we support from the SHPC controller. - */ -static int -pcishpc_disable_irqs(pcishpc_ctrl_t *ctrl_p) -{ - uint32_t reg; - int slot; - - reg = pcishpc_read_reg(ctrl_p, SHPC_CTRL_SERR_INT_REG); - - /* Mask all interrupts. */ - reg |= SHPC_SERR_INT_MASK_ALL; - - pcishpc_write_reg(ctrl_p, SHPC_CTRL_SERR_INT_REG, reg); - - /* Unmask the interrupts for each slot. */ - for (slot = 0; slot < ctrl_p->numSlotsImpl; slot++) { - reg = pcishpc_read_reg(ctrl_p, SHPC_LOGICAL_SLOT_REGS+slot); - - /* Disable/Mask all slot interrupts. */ - reg |= SHPC_SLOT_MASK_ALL; - - pcishpc_write_reg(ctrl_p, SHPC_LOGICAL_SLOT_REGS+slot, reg); - } - - pcishpc_debug("pcishpc_disable_irqs: ctrl_p 0x%p, " - "current bus speed 0x%x, slots connected 0x%x\n", ctrl_p, - ctrl_p->currBusSpeed, ctrl_p->numSlotsConn); - - return (DDI_SUCCESS); -} - - -/* - * pcishpc_register_slot() - * - * Create and register a slot with the Solaris HotPlug framework. - */ -static int -pcishpc_register_slot(pcishpc_ctrl_t *ctrl_p, int slot) -{ - pcishpc_t *pcishpc_p; - - pcishpc_p = pcishpc_create_slot(ctrl_p); - - ctrl_p->slots[slot] = pcishpc_p; - - pcishpc_p->slot_ops = hpc_alloc_slot_ops(KM_SLEEP); - - pcishpc_p->slot_ops->hpc_version = HPC_SLOT_OPS_VERSION; - - pcishpc_p->slotNum = slot; - - /* Setup the PCI device # for this SHPC slot. */ - if (ctrl_p->deviceIncreases) - pcishpc_p->deviceNum = ctrl_p->deviceStart + pcishpc_p->slotNum; - else - pcishpc_p->deviceNum = ctrl_p->deviceStart - pcishpc_p->slotNum; - - /* Setup the HPS framework slot ops callbacks for the SHPC driver. */ - pcishpc_p->slot_ops->hpc_op_connect = pcishpc_connect; - pcishpc_p->slot_ops->hpc_op_disconnect = pcishpc_disconnect; - pcishpc_p->slot_ops->hpc_op_control = pcishpc_pci_control; - /* PCI HPC drivers do not support the insert/remove callbacks. */ - pcishpc_p->slot_ops->hpc_op_insert = NULL; - pcishpc_p->slot_ops->hpc_op_remove = NULL; - - /* Setup the HPS framework slot information. */ - pcishpc_p->slot_info.version = HPC_SLOT_OPS_VERSION; - pcishpc_p->slot_info.slot_type = HPC_SLOT_TYPE_PCI; - /* Do not auto enable the deivce in this slot. */ - pcishpc_p->slot_info.slot_flags = HPC_SLOT_NO_AUTO_ENABLE | - HPC_SLOT_CREATE_DEVLINK; - - pcishpc_p->slot_info.slot.pci.device_number = pcishpc_p->deviceNum; - pcishpc_p->slot_info.slot.pci.slot_capabilities = HPC_SLOT_64BITS; - - /* setup thread for handling ATTN button events */ - if (ctrl_p->has_attn) { - pcishpc_debug("pcishpc_register_slot: " - "setting up ATTN button event " - "handler thread for slot %d\n", slot); - cv_init(&pcishpc_p->attn_btn_cv, NULL, CV_DRIVER, NULL); - pcishpc_p->attn_btn_pending = B_FALSE; - pcishpc_p->attn_btn_threadp = thread_create(NULL, 0, - pcishpc_attn_btn_handler, - (void *)pcishpc_p, 0, &p0, TS_RUN, minclsyspri); - pcishpc_p->attn_btn_thread_exit = B_FALSE; - } - - /* setup the slot name (used for ap-id) */ - pcishpc_set_slot_name(ctrl_p, slot); - - pcishpc_get_slot_state(pcishpc_p); - - /* Register this SHPC slot with the HPS framework. */ - if (hpc_slot_register(ctrl_p->shpc_dip, ctrl_p->nexus_path, - &pcishpc_p->slot_info, &pcishpc_p->slot_handle, - pcishpc_p->slot_ops, (caddr_t)pcishpc_p, 0) != 0) { - - pcishpc_debug("pcishpc_register_slot() failed to Register " - "slot"); - - hpc_free_slot_ops(pcishpc_p->slot_ops); - pcishpc_p->slot_ops = NULL; - - return (DDI_FAILURE); - } - - pcishpc_debug("pcishpc_register_slot() success for slot %d", slot); - - return (DDI_SUCCESS); -} - - -/* - * pcishpc_create_slot() - * - * Allocate and add a new HotPlug slot state structure to the linked list. - */ -static pcishpc_t * -pcishpc_create_slot(pcishpc_ctrl_t *ctrl_p) -{ - pcishpc_t *pcishpc_p; - - pcishpc_debug("pcishpc_create_slot() called(ctrl_p=%x)", ctrl_p); - - /* Allocate a new slot structure. */ - pcishpc_p = kmem_zalloc(sizeof (pcishpc_t), KM_SLEEP); - - pcishpc_p->ctrl = ctrl_p; - - mutex_enter(&pcishpc_list_mutex); - - /* Insert new slot into linked list of current slots. */ - pcishpc_p->nextp = pcishpc_head; - pcishpc_head = pcishpc_p; - - mutex_exit(&pcishpc_list_mutex); - - pcishpc_debug("pcishpc_create_slot() success"); - return (pcishpc_p); -} - -/* - * pcishpc_setup_controller() - * - * Get the number of HotPlug Slots, and the PCI device information - * for this HotPlug controller. - */ -static int -pcishpc_setup_controller(pcishpc_ctrl_t *ctrl_p) -{ - uint32_t config; - dev_info_t *ppdip; - - config = pcishpc_read_reg(ctrl_p, SHPC_SLOT_CONFIGURATION_REG); - - /* Get the number of HotPlug slots implemented */ - ctrl_p->numSlotsImpl = ((config)&31); - - /* - * Initilize the current bus speed and number of hotplug slots - * currently connected. - */ - ctrl_p->currBusSpeed = -1; - ctrl_p->numSlotsConn = 0; - - /* Save the value of Slots Available 1 and 2 registers */ - ctrl_p->shpc_slots_avail1_reg = pcishpc_read_reg(ctrl_p, - SHPC_SLOTS_AVAIL_I_REG); - ctrl_p->shpc_slots_avail2_reg = pcishpc_read_reg(ctrl_p, - SHPC_SLOTS_AVAIL_II_REG); - - /* Get the first PCI device Number used. */ - /* - * PCI-X I/O boat workaround. - * The register doesn't set up the correct value. - */ - ppdip = ddi_get_parent(ddi_get_parent(ctrl_p->shpc_dip)); - if ((ddi_prop_get_int(DDI_DEV_T_ANY, ppdip, DDI_PROP_DONTPASS, - "vendor-id", -1) == 0x108e) && - (ddi_prop_get_int(DDI_DEV_T_ANY, ppdip, DDI_PROP_DONTPASS, - "device-id", -1) == 0x9010)) - ctrl_p->deviceStart = 4; - else - ctrl_p->deviceStart = ((config>>8)&31); - - /* Get the first Physical device number. */ - ctrl_p->physStart = ((config>>16)&0x7ff); - /* Check if the device numbers increase or decrease. */ - ctrl_p->deviceIncreases = ((config>>29)&0x1); - - ctrl_p->has_attn = - (config & SHPC_SLOT_CONFIG_ATTN_BUTTON) ? B_TRUE : B_FALSE; - ctrl_p->has_mrl = - (config & SHPC_SLOT_CONFIG_MRL_SENSOR) ? B_TRUE : B_FALSE; - - cv_init(&ctrl_p->cmd_comp_cv, NULL, CV_DRIVER, NULL); - ctrl_p->command_complete = B_FALSE; - ctrl_p->arbiter_timeout = B_FALSE; - - if (ctrl_p->numSlotsImpl > MAX_SHPC_SLOTS) { - pcishpc_debug("pcishpc_setup_controller() too many SHPC " - "slots error"); - return (DDI_FAILURE); - } - - return (DDI_SUCCESS); -} - - -/* - * pcishpc_uninit() - * Unload the HogPlug controller driver and deallocate all resources. - */ -int -pcishpc_uninit(dev_info_t *dip) -{ - pcishpc_ctrl_t *ctrl_p; - - pcishpc_debug("pcishpc_uninit() called(dip=%p)", dip); - - mutex_enter(&pcishpc_control_mutex); - - ctrl_p = pcishpc_get_controller(dip); - - if (!ctrl_p) { - pcishpc_debug("pcishpc_uninit() Unable to find softstate"); - mutex_exit(&pcishpc_control_mutex); - return (DDI_FAILURE); - } - - (void) pcishpc_disable_irqs(ctrl_p); - ctrl_p->interrupt_installed = B_FALSE; - - (void) pcishpc_destroy_controller(dip); - - mutex_exit(&pcishpc_control_mutex); - - pcishpc_debug("pcishpc_uninit() success(dip=%p)", dip); - - return (DDI_SUCCESS); -} - -/* - * pcishpc_destroy_slots() - * - * Free up all of the slot resources for this controller. - */ -static int -pcishpc_destroy_slots(pcishpc_ctrl_t *ctrl_p) -{ - pcishpc_t *pcishpc_p; - pcishpc_t **pcishpc_pp; - - pcishpc_debug("pcishpc_destroy_slots() called(ctrl_p=%p)", ctrl_p); - - pcishpc_pp = &pcishpc_head; - - while ((pcishpc_p = *pcishpc_pp) != NULL) { - if (pcishpc_p->ctrl == ctrl_p) { - if (pcishpc_p->attn_btn_threadp != NULL) { - mutex_enter(&ctrl_p->shpc_mutex); - pcishpc_p->attn_btn_thread_exit = B_TRUE; - cv_signal(&pcishpc_p->attn_btn_cv); - pcishpc_debug("pcishpc_destroy_slots: " - "waiting for ATTN thread exit\n"); - cv_wait(&pcishpc_p->attn_btn_cv, - &ctrl_p->shpc_mutex); - pcishpc_debug("pcishpc_destroy_slots: " - "ATTN thread exit\n"); - cv_destroy(&pcishpc_p->attn_btn_cv); - pcishpc_p->attn_btn_threadp = NULL; - mutex_exit(&ctrl_p->shpc_mutex); - } - - *pcishpc_pp = pcishpc_p->nextp; - - pcishpc_debug("pcishpc_destroy_slots() (shpc_p=%p) " - "destroyed", pcishpc_p); - if (pcishpc_p->slot_ops) - if (hpc_slot_unregister( - &pcishpc_p->slot_handle) != 0) { - pcishpc_debug("pcishpc_destroy_slots() " - "failed to unregister slot"); - return (DDI_FAILURE); - } else { - hpc_free_slot_ops(pcishpc_p->slot_ops); - pcishpc_p->slot_ops = NULL; - } - kmem_free(pcishpc_p, sizeof (pcishpc_t)); - } else - pcishpc_pp = &(pcishpc_p->nextp); - } - - return (DDI_SUCCESS); -} - - -/* - * pcishpc_connect() - * - * Called by Hot Plug Services to connect a slot on the bus. - */ -/*ARGSUSED*/ -static int -pcishpc_connect(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags) -{ - pcishpc_t *pcishpc_p; - uint32_t status; - - pcishpc_debug("pcishpc_connect called()"); - - pcishpc_p = pcishpc_hpc_get_slot_state(slot_hdl); - - if (!pcishpc_p) { - pcishpc_debug("pcishpc_connect() " - "Failed to find soft state for slot_hdl %x", slot_hdl); - return (HPC_ERR_FAILED); - } - - mutex_enter(&pcishpc_p->ctrl->shpc_mutex); - - /* make sure the MRL sensor is closed */ - status = pcishpc_read_reg(pcishpc_p->ctrl, - SHPC_LOGICAL_SLOT_REGS+pcishpc_p->slotNum); - - if (status & SHPC_SLOT_MRL_STATE_MASK) { - pcishpc_debug("pcishpc_connect() failed: MRL open"); - goto cleanup; - } - - if (pcishpc_set_power_state(pcishpc_p, HPC_SLOT_CONNECTED) != - DDI_SUCCESS) { - pcishpc_debug("pcishpc_connect() failed: set power state"); - goto cleanup; - } - - mutex_exit(&pcishpc_p->ctrl->shpc_mutex); - - pcishpc_debug("pcishpc_connect() success!"); - - return (HPC_SUCCESS); - -cleanup: - mutex_exit(&pcishpc_p->ctrl->shpc_mutex); - return (HPC_ERR_FAILED); -} - - -/* - * pcishpc_set_power_state() - * - * Changed a slot's power state. - */ -static int -pcishpc_set_power_state(pcishpc_t *pcishpc_p, hpc_slot_state_t state) -{ - pcishpc_get_slot_state(pcishpc_p); - - /* Check to see if the slot is already in this state. */ - if (pcishpc_p->slot_state == state) { - pcishpc_debug("pcishpc_set_power_state() slot already in " - "this state"); - return (DDI_SUCCESS); - } - - if ((pcishpc_p->slot_state == HPC_SLOT_EMPTY) && - ((state == HPC_SLOT_CONNECTED) || - (state == HPC_SLOT_DISCONNECTED))) { - pcishpc_debug("pcishpc_set_power_state() slot in " - "empty state"); - return (DDI_FAILURE); - } - - /* Set the Power LED to blink. */ - (void) pcishpc_setled(pcishpc_p, HPC_POWER_LED, HPC_LED_BLINK); - - /* Turn all other LEDS off. */ - (void) pcishpc_setled(pcishpc_p, HPC_FAULT_LED, HPC_LED_OFF); - (void) pcishpc_setled(pcishpc_p, HPC_ATTN_LED, HPC_LED_OFF); - (void) pcishpc_setled(pcishpc_p, HPC_ACTIVE_LED, HPC_LED_OFF); - - /* Set the slot state to the new slot state. */ - pcishpc_p->slot_state = state; - - /* Set the bus speed only if the bus segment is not running */ - if (state == HPC_SLOT_CONNECTED) { - if (pcishpc_set_bus_speed(pcishpc_p) != DDI_SUCCESS) - return (DDI_FAILURE); - - pcishpc_p->ctrl->numSlotsConn++; - } else { - if (--pcishpc_p->ctrl->numSlotsConn == 0) - pcishpc_p->ctrl->currBusSpeed = -1; - } - - pcishpc_debug("pcishpc_set_power_state(): ctrl_p 0x%p, " - "pcishpc_p 0x%p, slot state 0x%x, current bus speed 0x%x, " - "slots connected 0x%x\n", pcishpc_p->ctrl, pcishpc_p, state, - pcishpc_p->ctrl->currBusSpeed, pcishpc_p->ctrl->numSlotsConn); - - /* Mask or Unmask MRL Sensor SEER bit based on new slot state */ - if (pcishpc_p->ctrl->has_mrl == B_TRUE) { - uint32_t reg; - - reg = pcishpc_read_reg(pcishpc_p->ctrl, - SHPC_LOGICAL_SLOT_REGS+pcishpc_p->slotNum); - reg = (state == HPC_SLOT_CONNECTED) ? - (reg & ~SHPC_SLOT_MRL_SERR_MASK) : - (reg | SHPC_SLOT_MRL_SERR_MASK); - - pcishpc_write_reg(pcishpc_p->ctrl, - SHPC_LOGICAL_SLOT_REGS+pcishpc_p->slotNum, reg); - } - - /* Update the hardweare slot state. */ - if (pcishpc_set_slot_state(pcishpc_p) != DDI_SUCCESS) { - pcishpc_debug("pcishpc_set_power_state() failed"); - (void) pcishpc_setled(pcishpc_p, HPC_POWER_LED, HPC_LED_OFF); - pcishpc_get_slot_state(pcishpc_p); - return (DDI_FAILURE); - } - - /* Turn the Power LED ON for a connected slot. */ - if (state == HPC_SLOT_CONNECTED) { - (void) pcishpc_setled(pcishpc_p, HPC_POWER_LED, HPC_LED_ON); - } - - /* Turn the Power LED OFF for a disconnected slot. */ - if (state == HPC_SLOT_DISCONNECTED) { - (void) pcishpc_setled(pcishpc_p, HPC_POWER_LED, HPC_LED_OFF); - } - - /* Turn all other LEDS off. */ - (void) pcishpc_setled(pcishpc_p, HPC_FAULT_LED, HPC_LED_OFF); - (void) pcishpc_setled(pcishpc_p, HPC_ATTN_LED, HPC_LED_OFF); - (void) pcishpc_setled(pcishpc_p, HPC_ACTIVE_LED, HPC_LED_OFF); - - pcishpc_debug("pcishpc_set_power_state() success!"); - - pcishpc_get_slot_state(pcishpc_p); - - /* delay after powerON to let the device initialize itself */ - delay(drv_usectohz(pcishpc_reset_delay)); - - return (DDI_SUCCESS); -} - -/* - * pcishpc_set_bus_speed() - * - * Set the bus speed and mode. - */ -static int -pcishpc_set_bus_speed(pcishpc_t *pcishpc_p) -{ - pcishpc_ctrl_t *ctrl_p = pcishpc_p->ctrl; - int curr_speed = ctrl_p->currBusSpeed; - int speed = -1; - int avail_slots; - uint32_t status; - - /* Make sure that the slot is in a correct state */ - status = pcishpc_read_reg(ctrl_p, - SHPC_LOGICAL_SLOT_REGS+pcishpc_p->slotNum); - - /* Return failure if the slot is empty */ - if ((status & SHPC_SLOT_CARD_EMPTY_MASK) == - SHPC_SLOT_CARD_EMPTY_MASK) { - pcishpc_debug("pcishpc_set_bus_speed() failed: " - "the slot is empty."); - return (DDI_FAILURE); - } - - /* Return failure if the slot is not in disabled state */ - if ((status & SHPC_SLOT_STATE_MASK) != SHPC_SLOT_DISABLED) { - pcishpc_debug("pcishpc_set_bus_speed() failed: " - "incorrect slot state."); - return (DDI_FAILURE); - } - - /* Set the "power-only" mode for the slot */ - if (pcishpc_issue_command(ctrl_p, ((1+pcishpc_p->slotNum)<<8) | - SHPC_SLOT_POWER_ONLY) != DDI_SUCCESS) { - pcishpc_debug("pcishpc_set_bus_speed() failed to set " - "the slot %d in the power-only mode", pcishpc_p->slotNum); - return (DDI_FAILURE); - } - - /* Wait for power good */ - delay(drv_usectohz(SHPC_POWER_GOOD_WAIT_TIME)); - - /* Make sure that the slot is in "power-only" state */ - status = pcishpc_read_reg(ctrl_p, - SHPC_LOGICAL_SLOT_REGS+pcishpc_p->slotNum); - - if ((status & SHPC_SLOT_STATE_MASK) != SHPC_SLOT_POWER_ONLY) { - pcishpc_debug("pcishpc_set_bus_speed() " - "power-only failed: incorrect slot state."); - return (DDI_FAILURE); - } - - /* - * Check if SHPC has available slots and select the highest - * available bus speed for the slot. - * - * The bus speed codes are: - * 100 - 133Mhz; <--+ - * 011 - 100Mhz; <--+ PCI-X - * 010 - 66Mhz; <--+ - * - * 001 - 66Mhz; <--+ - * 000 - 33Mhz <--+ Conv PCI - */ - switch (status & SHPC_SLOT_PCIX_CAPABLE_MASK) { - case SHPC_SLOT_133MHZ_PCIX_CAPABLE: - avail_slots = (ctrl_p->shpc_slots_avail1_reg >> - SHPC_AVAIL_133MHZ_PCIX_SPEED_SHIFT) & SHPC_AVAIL_SPEED_MASK; - - if (((curr_speed == -1) && avail_slots) || - (curr_speed == SHPC_SBCR_133MHZ_PCIX_SPEED)) { - speed = SHPC_SBCR_133MHZ_PCIX_SPEED; - break; - } - /* FALLTHROUGH */ - case SHPC_SLOT_100MHZ_PCIX_CAPABLE: - avail_slots = (ctrl_p->shpc_slots_avail1_reg >> - SHPC_AVAIL_100MHZ_PCIX_SPEED_SHIFT) & SHPC_AVAIL_SPEED_MASK; - - if (((curr_speed == -1) && avail_slots) || - (curr_speed == SHPC_SBCR_100MHZ_PCIX_SPEED)) { - speed = SHPC_SBCR_100MHZ_PCIX_SPEED; - break; - } - /* FALLTHROUGH */ - case SHPC_SLOT_66MHZ_PCIX_CAPABLE: - avail_slots = (ctrl_p->shpc_slots_avail1_reg >> - SHPC_AVAIL_66MHZ_PCIX_SPEED_SHIFT) & SHPC_AVAIL_SPEED_MASK; - - if (((curr_speed == -1) && avail_slots) || - (curr_speed == SHPC_SBCR_66MHZ_PCIX_SPEED)) { - speed = SHPC_SBCR_66MHZ_PCIX_SPEED; - break; - } - /* FALLTHROUGH */ - default: - avail_slots = (ctrl_p->shpc_slots_avail2_reg >> - SHPC_AVAIL_66MHZ_CONV_SPEED_SHIFT) & SHPC_AVAIL_SPEED_MASK; - - if ((status & SHPC_SLOT_66MHZ_CONV_CAPABLE) && - (((curr_speed == -1) && avail_slots) || - (curr_speed == SHPC_SBCR_66MHZ_CONV_SPEED))) { - speed = SHPC_SBCR_66MHZ_CONV_SPEED; - } else { - avail_slots = (ctrl_p->shpc_slots_avail1_reg >> - SHPC_AVAIL_33MHZ_CONV_SPEED_SHIFT) & - SHPC_AVAIL_SPEED_MASK; - - if (((curr_speed == -1) && (avail_slots)) || - (curr_speed == SHPC_SBCR_33MHZ_CONV_SPEED)) { - speed = SHPC_SBCR_33MHZ_CONV_SPEED; - } else { - pcishpc_debug("pcishpc_set_bus_speed() " - " failed to set the bus speed, slot# %d", - pcishpc_p->slotNum); - return (DDI_FAILURE); - } - } - break; - } - - /* - * If the bus segment is already running, check to see the card - * in the slot can support the current bus speed. - */ - if (curr_speed == speed) { - /* - * Check to see there is any slot available for the current - * bus speed. Otherwise, we need fail the current slot connect - * request. - */ - return ((avail_slots <= ctrl_p->numSlotsConn) ? - DDI_FAILURE : DDI_SUCCESS); - } - - /* Set the bus speed */ - if (pcishpc_issue_command(ctrl_p, SHPC_COMM_STS_SET_SPEED | - speed) == DDI_FAILURE) { - pcishpc_debug("pcishpc_set_bus_speed() failed " - "to set bus %d speed", pcishpc_p->slotNum); - return (DDI_FAILURE); - } - - /* Check the current bus speed */ - status = pcishpc_read_reg(ctrl_p, SHPC_PROF_IF_SBCR_REG) & - SHPC_SBCR_SPEED_MASK; - if ((status & SHPC_SBCR_SPEED_MASK) != speed) { - pcishpc_debug("pcishpc_set_bus_speed() an incorrect " - "bus speed, slot = 0x%x, speed = 0x%x", - pcishpc_p->slotNum, status & SHPC_SBCR_SPEED_MASK); - return (DDI_FAILURE); - } - - - /* Save the current bus speed */ - ctrl_p->currBusSpeed = speed; - - return (DDI_SUCCESS); -} - -/* - * pcishpc_disconnect() - * - * Called by Hot Plug Services to disconnect a slot on the bus. - */ -/*ARGSUSED*/ -static int -pcishpc_disconnect(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, - uint_t flags) -{ - pcishpc_t *pcishpc_p; - - pcishpc_debug("pcishpc_disconnect called()"); - - pcishpc_p = pcishpc_hpc_get_slot_state(slot_hdl); - - if (!pcishpc_p) { - pcishpc_debug("pcishpc_disconnect() " - "Failed to find soft state for slot_hdl %x", slot_hdl); - return (HPC_ERR_FAILED); - } - - mutex_enter(&pcishpc_p->ctrl->shpc_mutex); - - if (pcishpc_set_power_state(pcishpc_p, HPC_SLOT_DISCONNECTED) - != DDI_SUCCESS) { - pcishpc_debug("pcishpc_disconnect() failed"); - goto cleanup; - } - - mutex_exit(&pcishpc_p->ctrl->shpc_mutex); - - pcishpc_debug("pcishpc_disconnect() success!"); - - return (HPC_SUCCESS); - -cleanup: - mutex_exit(&pcishpc_p->ctrl->shpc_mutex); - return (HPC_ERR_FAILED); -} - - -/* - * pcishpc_pci_control() - * - * Called by Hot Plug Services to perform a attachment point specific - * operation on a Hot Pluggable Standard PCI Slot. - */ -/*ARGSUSED*/ -static int -pcishpc_pci_control(caddr_t ops_arg, hpc_slot_t slot_hdl, int request, - caddr_t arg) -{ - hpc_slot_state_t *hpc_slot_state; - hpc_board_type_t *hpc_board_type; - hpc_led_info_t *hpc_led_info; - pcishpc_t *pcishpc_p; - int ret = HPC_SUCCESS; - - pcishpc_debug("pcishpc_pci_control called(Request %s)", - pcishpc_textrequest(request)); - - pcishpc_p = pcishpc_hpc_get_slot_state(slot_hdl); - - if (!pcishpc_p) { - pcishpc_debug("pcishpc_pci_control() Error: " - "Failed to find soft state for slot_hdl %x", slot_hdl); - return (HPC_ERR_FAILED); - } - - mutex_enter(&pcishpc_p->ctrl->shpc_mutex); - - switch (request) { - case HPC_CTRL_GET_SLOT_STATE: - hpc_slot_state = (hpc_slot_state_t *)arg; - pcishpc_get_slot_state(pcishpc_p); - *hpc_slot_state = pcishpc_p->slot_state; - pcishpc_debug("pcishpc_pci_control() - " - "HPC_CTRL_GET_SLOT_STATE (state=%s)", - pcishpc_textslotstate(pcishpc_p->slot_state)); - break; - - case HPC_CTRL_GET_BOARD_TYPE: - hpc_board_type = (hpc_board_type_t *)arg; - pcishpc_debug("pcishpc_pci_control() - " - "HPC_CTRL_GET_BOARD_TYPE"); - pcishpc_get_slot_state(pcishpc_p); - /* - * The HPS framework does not know what board - * type is plugged in. - */ - if (pcishpc_p->slot_state == HPC_SLOT_EMPTY) - *hpc_board_type = HPC_BOARD_UNKNOWN; - else - *hpc_board_type = HPC_BOARD_PCI_HOTPLUG; - break; - - case HPC_CTRL_GET_LED_STATE: - hpc_led_info = (hpc_led_info_t *)arg; - - pcishpc_get_slot_state(pcishpc_p); - - switch (hpc_led_info->led) { - case HPC_FAULT_LED: - hpc_led_info->state = - pcishpc_p->fault_led_state; - pcishpc_debug("pcishpc_pci_control() - " - "GET_LED FAULT (state=%s)", - pcishpc_textledstate( - hpc_led_info->state)); - break; - - case HPC_POWER_LED: - hpc_led_info->state = - pcishpc_p->power_led_state; - pcishpc_debug("pcishpc_pci_control() - " - "GET_LED POWER (state=%s)", - pcishpc_textledstate( - hpc_led_info->state)); - break; - - case HPC_ATTN_LED: - hpc_led_info->state = - pcishpc_p->attn_led_state; - pcishpc_debug("pcishpc_pci_control() - " - "GET_LED ATTN(state = %s)", - pcishpc_textledstate( - hpc_led_info->state)); - break; - - case HPC_ACTIVE_LED: - hpc_led_info->state = - pcishpc_p->active_led_state; - pcishpc_debug("pcishpc_pci_control() - " - "GET_LED ACTIVE(state = %s)", - pcishpc_textledstate( - hpc_led_info->state)); - break; - - default: - pcishpc_debug("pcishpc_pci_control() " - "Error: GET_LED - " - "Invalid LED %x", - hpc_led_info->led); - ret = HPC_ERR_NOTSUPPORTED; - break; - } - break; - - case HPC_CTRL_SET_LED_STATE: - hpc_led_info = (hpc_led_info_t *)arg; - switch (hpc_led_info->led) { - case HPC_ATTN_LED: - (void) pcishpc_setled(pcishpc_p, - hpc_led_info->led, - hpc_led_info->state); - break; - case HPC_POWER_LED: - pcishpc_debug("pcishpc_pci_control() " - "Error: SET_LED - power LED"); - ret = HPC_ERR_NOTSUPPORTED; - break; - case HPC_FAULT_LED: - case HPC_ACTIVE_LED: - break; - default: - pcishpc_debug("pcishpc_pci_control() " - "Error: SET_LED - Unknown LED %x", - hpc_led_info->led); - ret = HPC_ERR_NOTSUPPORTED; - break; - } - break; - - case HPC_CTRL_DEV_UNCONFIG_FAILURE: - case HPC_CTRL_DEV_CONFIG_FAILURE: - pcishpc_debug("pcishpc_pci_control() Config/Unconfig " - "failed."); - (void) pcishpc_setled(pcishpc_p, HPC_ATTN_LED, - HPC_LED_BLINK); - break; - - case HPC_CTRL_ENABLE_AUTOCFG: - case HPC_CTRL_DISABLE_AUTOCFG: - case HPC_CTRL_DISABLE_SLOT: - case HPC_CTRL_DEV_UNCONFIGURED: - case HPC_CTRL_ENABLE_SLOT: - case HPC_CTRL_DISABLE_ENUM: - case HPC_CTRL_DEV_UNCONFIG_START: - case HPC_CTRL_DEV_CONFIG_START: - case HPC_CTRL_DEV_CONFIGURED: - pcishpc_debug("pcishpc_pci_control() - %s", - pcishpc_textrequest(request)); - break; - - case HPC_CTRL_ENABLE_ENUM: - default: - pcishpc_debug("pcishpc_pci_control() - Error: " - "request (%d) NOT SUPPORTED", request); - ret = HPC_ERR_NOTSUPPORTED; - break; - } - - mutex_exit(&pcishpc_p->ctrl->shpc_mutex); - return (ret); -} - - -/* - * pcishpc_setled() - * - * Change the state of a slot's LED. - */ -static int -pcishpc_setled(pcishpc_t *pcishpc_p, hpc_led_t led, hpc_led_state_t state) -{ - switch (led) { - case HPC_FAULT_LED: - pcishpc_debug("pcishpc_setled() - HPC_FAULT_LED " - "(set %s)", pcishpc_textledstate(state)); - pcishpc_p->fault_led_state = state; - break; - - case HPC_POWER_LED: - pcishpc_debug("pcishpc_setled() - HPC_POWER_LED " - "(set %s)", pcishpc_textledstate(state)); - pcishpc_p->power_led_state = state; - break; - - case HPC_ATTN_LED: - pcishpc_debug("pcishpc_setled() - HPC_ATTN_LED " - "(set %s)", pcishpc_textledstate(state)); - pcishpc_p->attn_led_state = state; - break; - - case HPC_ACTIVE_LED: - pcishpc_debug("pcishpc_setled() - HPC_ACTIVE_LED " - "(set %s)", pcishpc_textledstate(state)); - pcishpc_p->active_led_state = state; - break; - } - - return (pcishpc_set_slot_state(pcishpc_p)); -} - - -/* - * pcishpc_set_slot_state() - * - * Updates the slot's state and leds. - */ -static int -pcishpc_set_slot_state(pcishpc_t *pcishpc_p) -{ - uint32_t reg; - uint32_t cmd_code; - hpc_slot_state_t slot_state; - - reg = pcishpc_read_reg(pcishpc_p->ctrl, - SHPC_LOGICAL_SLOT_REGS+pcishpc_p->slotNum); - - /* Default all states to unchanged. */ - cmd_code = ((1+pcishpc_p->slotNum)<<8); - - /* Has the slot state changed? */ - if ((reg & SHPC_SLOT_CARD_EMPTY_MASK) == SHPC_SLOT_CARD_EMPTY_MASK) - slot_state = HPC_SLOT_EMPTY; - else - slot_state = pcishpc_slot_shpc_to_hpc(reg & 3); - if (pcishpc_p->slot_state != slot_state) { - pcishpc_debug("pcishpc_set_slot_state() Slot State changed"); - /* Set the new slot state in the Slot operation command. */ - cmd_code |= pcishpc_slot_hpc_to_shpc(pcishpc_p->slot_state); - } - - /* Has the Power LED state changed? */ - if (pcishpc_p->power_led_state != pcishpc_led_shpc_to_hpc((reg>>2)&3)) { - pcishpc_debug("pcishpc_set_slot_state() Power LED State " - "changed"); - /* Set the new power led state in the Slot operation command. */ - cmd_code |= - (pcishpc_led_hpc_to_shpc(pcishpc_p->power_led_state) - << 2); - } - - /* Has the Attn LED state changed? */ - if (pcishpc_p->attn_led_state != pcishpc_led_shpc_to_hpc((reg>>4)&3)) { - pcishpc_debug("pcishpc_set_slot_state() Attn LED State " - "changed"); - /* Set the new attn led state in the Slot operation command. */ - cmd_code |= (pcishpc_led_hpc_to_shpc(pcishpc_p->attn_led_state) - << 4); - } - - return (pcishpc_issue_command(pcishpc_p->ctrl, cmd_code)); -} - - -/* - * pcishpc_wait_busy() - * - * Wait until the SHPC controller is not busy. - */ -static int -pcishpc_wait_busy(pcishpc_ctrl_t *ctrl_p) -{ - uint32_t status; - - /* Wait until SHPC controller is NOT busy */ - /*CONSTCOND*/ - while (1) { - status = pcishpc_read_reg(ctrl_p, SHPC_COMMAND_STATUS_REG); - - /* Is there an MRL Sensor error? */ - if ((status & SHPC_COMM_STS_ERR_MASK) == - SHPC_COMM_STS_ERR_MRL_OPEN) { - pcishpc_debug("pcishpc_wait_busy() ERROR: MRL Sensor " - "error"); - break; - } - - /* Is there an Invalid command error? */ - if ((status & SHPC_COMM_STS_ERR_MASK) == - SHPC_COMM_STS_ERR_INVALID_COMMAND) { - pcishpc_debug("pcishpc_wait_busy() ERROR: Invalid " - "command error"); - break; - } - - /* Is there an Invalid Speed/Mode error? */ - if ((status & SHPC_COMM_STS_ERR_MASK) == - SHPC_COMM_STS_ERR_INVALID_SPEED) { - pcishpc_debug("pcishpc_wait_busy() ERROR: Invalid " - "Speed/Mode error"); - break; - } - - /* Is the SHPC controller not BUSY? */ - if (!(status & SHPC_COMM_STS_CTRL_BUSY)) { - /* Return Success. */ - return (DDI_SUCCESS); - } - - pcishpc_debug("pcishpc_wait_busy() SHPC controller busy. " - "Waiting"); - - /* Wait before polling the status register again. */ - delay(drv_usectohz(SHPC_COMMAND_WAIT_TIME)); - } - - return (DDI_FAILURE); -} - - -/* - * pcishpc_issue_command() - * - * Sends a command to the SHPC controller. - */ -static int -pcishpc_issue_command(pcishpc_ctrl_t *ctrl_p, uint32_t cmd_code) -{ - int retCode; - - pcishpc_debug("pcishpc_issue_command() cmd_code=%02x", cmd_code); - - mutex_enter(&ctrl_p->shpc_intr_mutex); - - ctrl_p->command_complete = B_FALSE; - - /* Write the command to the SHPC controller. */ - pcishpc_write_reg(ctrl_p, SHPC_COMMAND_STATUS_REG, cmd_code); - - while (ctrl_p->command_complete == B_FALSE) - cv_wait(&ctrl_p->cmd_comp_cv, &ctrl_p->shpc_intr_mutex); - - /* Wait until the SHPC controller processes the command. */ - retCode = pcishpc_wait_busy(ctrl_p); - - /* Make sure the command completed. */ - if (retCode == DDI_SUCCESS) { - /* Did the command fail to generate the command complete IRQ? */ - if (ctrl_p->command_complete != B_TRUE) { - pcishpc_debug("pcishpc_issue_command() Failed on " - "generate cmd complete IRQ"); - retCode = DDI_FAILURE; - } - } - - mutex_exit(&ctrl_p->shpc_intr_mutex); - - if (retCode == DDI_FAILURE) - pcishpc_debug("pcishpc_issue_command() Failed on cmd_code=%02x", - cmd_code); - else - pcishpc_debug("pcishpc_issue_command() Success on " - "cmd_code=%02x", cmd_code); - - return (retCode); -} - -/* - * pcishpc_led_shpc_to_hpc() - * - * Convert from SHPC indicator status to HPC indicator status. - */ -static int -pcishpc_led_shpc_to_hpc(int state) -{ - switch (state) { - case 1: /* SHPC On bits b01 */ - return (HPC_LED_ON); - case 2: /* SHPC Blink bits b10 */ - return (HPC_LED_BLINK); - case 3: /* SHPC Off bits b11 */ - return (HPC_LED_OFF); - } - - return (HPC_LED_OFF); -} - - -/* - * pcishpc_led_hpc_to_shpc() - * - * Convert from HPC indicator status to SHPC indicator status. - */ -static int -pcishpc_led_hpc_to_shpc(int state) -{ - switch (state) { - case HPC_LED_ON: - return (1); /* SHPC On bits b01 */ - case HPC_LED_BLINK: - return (2); /* SHPC Blink bits b10 */ - case HPC_LED_OFF: - return (3); /* SHPC Off bits b11 */ - } - - return (3); /* SHPC Off bits b11 */ -} - -/* - * pcishpc_slot_shpc_to_hpc() - * - * Convert from SHPC slot state to HPC slot state. - */ -static int -pcishpc_slot_shpc_to_hpc(int state) -{ - switch (state) { - case 0: /* SHPC Reserved */ - return (HPC_SLOT_EMPTY); - - case 1: /* SHPC Powered Only */ - return (HPC_SLOT_UNKNOWN); - - case 2: /* SHPC Enabled */ - return (HPC_SLOT_CONNECTED); - - case 3: /* SHPC Disabled */ - return (HPC_SLOT_DISCONNECTED); - } - - /* Unknown slot state. */ - return (HPC_SLOT_UNKNOWN); -} - - -/* - * pcishpc_slot_hpc_to_shpc() - * - * Convert from HPC slot state to SHPC slot state. - */ -static int -pcishpc_slot_hpc_to_shpc(int state) -{ - switch (state) { - case HPC_SLOT_EMPTY: - return (0); /* SHPC Reserved */ - - case HPC_SLOT_UNKNOWN: - return (1); /* SHPC Powered Only */ - - case HPC_SLOT_CONNECTED: - return (2); /* SHPC Enabled */ - - case HPC_SLOT_DISCONNECTED: - return (3); /* SHPC Disabled */ - } - - /* Known slot state is reserved. */ - return (0); -} - - -/* - * pcishpc_get_slot_state() - * - * Get the state of the slot. - */ -static void -pcishpc_get_slot_state(pcishpc_t *pcishpc_p) -{ - uint32_t reg; - - /* Read the logical slot register for this Slot. */ - reg = pcishpc_read_reg(pcishpc_p->ctrl, - SHPC_LOGICAL_SLOT_REGS+pcishpc_p->slotNum); - - /* Convert from the SHPC slot state to the HPC slot state. */ - if ((reg & SHPC_SLOT_CARD_EMPTY_MASK) == SHPC_SLOT_CARD_EMPTY_MASK) - pcishpc_p->slot_state = HPC_SLOT_EMPTY; - else - pcishpc_p->slot_state = pcishpc_slot_shpc_to_hpc(reg & 3); - - /* Convert from the SHPC Power LED state to the HPC Power LED state. */ - pcishpc_p->power_led_state = pcishpc_led_shpc_to_hpc((reg>>2)&3); - - /* Convert from the SHPC Attn LED state to the HPC Attn LED state. */ - pcishpc_p->attn_led_state = pcishpc_led_shpc_to_hpc((reg>>4)&3); - - /* We don't have a fault LED so just default it to OFF. */ - pcishpc_p->fault_led_state = HPC_LED_OFF; - - /* We don't have an active LED so just default it to OFF. */ - pcishpc_p->active_led_state = HPC_LED_OFF; -} - -/* - * pcishpc_textledstate() - * - * Convert the led state into a text message. - */ -static char * -pcishpc_textledstate(hpc_led_state_t state) -{ - /* Convert an HPC led state into a textual string. */ - switch (state) { - case HPC_LED_OFF: - return ("off"); - - case HPC_LED_ON: - return ("on"); - - case HPC_LED_BLINK: - return ("blink"); - } - return ("unknown"); -} - -/* - * pcishpc_textrequest() - * - * Convert the request into a text message. - */ -static char * -pcishpc_textrequest(int request) -{ - /* Convert an HPC request into a textual string. */ - switch (request) { - case HPC_CTRL_GET_LED_STATE: - return ("HPC_CTRL_GET_LED_STATE"); - case HPC_CTRL_SET_LED_STATE: - return ("HPC_CTRL_SET_LED_STATE"); - case HPC_CTRL_GET_SLOT_STATE: - return ("HPC_CTRL_GET_SLOT_STATE"); - case HPC_CTRL_DEV_CONFIGURED: - return ("HPC_CTRL_DEV_CONFIGURED"); - case HPC_CTRL_DEV_UNCONFIGURED: - return ("HPC_CTRL_DEV_UNCONFIGURED"); - case HPC_CTRL_GET_BOARD_TYPE: - return ("HPC_CTRL_GET_BOARD_TYPE"); - case HPC_CTRL_DISABLE_AUTOCFG: - return ("HPC_CTRL_DISABLE_AUTOCFG"); - case HPC_CTRL_ENABLE_AUTOCFG: - return ("HPC_CTRL_ENABLE_AUTOCFG"); - case HPC_CTRL_DISABLE_SLOT: - return ("HPC_CTRL_DISABLE_SLOT"); - case HPC_CTRL_ENABLE_SLOT: - return ("HPC_CTRL_ENABLE_SLOT"); - case HPC_CTRL_DISABLE_ENUM: - return ("HPC_CTRL_DISABLE_ENUM"); - case HPC_CTRL_ENABLE_ENUM: - return ("HPC_CTRL_ENABLE_ENUM"); - case HPC_CTRL_DEV_CONFIG_FAILURE: - return ("HPC_CTRL_DEV_CONFIG_FAILURE"); - case HPC_CTRL_DEV_UNCONFIG_FAILURE: - return ("HPC_CTRL_DEV_UNCONFIG_FAILURE"); - case HPC_CTRL_DEV_CONFIG_START: - return ("HPC_CTRL_DEV_CONFIG_START"); - case HPC_CTRL_DEV_UNCONFIG_START: - return ("HPC_CTRL_DEV_UNCONFIG_START"); - } - return ("Unknown"); -} - -/* - * pcishpc_textslotstate() - * - * Convert the request into a text message. - */ -static char * -pcishpc_textslotstate(hpc_slot_state_t state) -{ - /* Convert an HPC slot state into a textual string. */ - switch (state) { - case HPC_SLOT_EMPTY: - return ("HPC_SLOT_EMPTY"); - case HPC_SLOT_DISCONNECTED: - return ("HPC_SLOT_DISCONNECTED"); - case HPC_SLOT_CONNECTED: - return ("HPC_SLOT_CONNECTED"); - case HPC_SLOT_UNKNOWN: - return ("HPC_SLOT_UNKNOWN"); - } - return ("Unknown"); -} - - -/* - * pcishpc_write_reg() - * - * Write to a SHPC controller register. - */ -static void -pcishpc_write_reg(pcishpc_ctrl_t *ctrl_p, int reg, uint32_t data) -{ - /* Setup the SHPC dword select register. */ - pci_config_put8(ctrl_p->shpc_config_hdl, - ctrl_p->shpc_dword_select, (uint8_t)reg); - - /* Read back the SHPC dword select register and verify. */ - if (pci_config_get8(ctrl_p->shpc_config_hdl, - ctrl_p->shpc_dword_select) != (uint8_t)reg) { - pcishpc_debug("pcishpc_write_reg() - Failed writing " - "DWORD select reg"); - return; - } - - /* Write to the SHPC dword data register. */ - pci_config_put32(ctrl_p->shpc_config_hdl, - ctrl_p->shpc_dword_data_reg, data); - - /* - * Issue a read of the VendorID/DeviceID just to force the previous - * write to complete. This is probably not necessary, but it does - * help enforce ordering if there is an issue. - */ - (void) pci_config_get16(ctrl_p->shpc_config_hdl, PCI_CONF_VENID); -} - - -/* - * pcishpc_read_reg() - * - * Read from a SHPC controller register. - */ -static uint32_t -pcishpc_read_reg(pcishpc_ctrl_t *ctrl_p, int reg) -{ - /* Setup the SHPC dword select register. */ - pci_config_put8(ctrl_p->shpc_config_hdl, - ctrl_p->shpc_dword_select, (uint8_t)reg); - - /* Read back the SHPC dword select register and verify. */ - if (pci_config_get8(ctrl_p->shpc_config_hdl, - ctrl_p->shpc_dword_select) != (uint8_t)reg) { - pcishpc_debug("pcishpc_read_reg() - Failed writing DWORD " - "select reg"); - return (0xFFFFFFFF); - } - - /* Read from the SHPC dword data register. */ - return (pci_config_get32(ctrl_p->shpc_config_hdl, - ctrl_p->shpc_dword_data_reg)); -} - - -/* - * pcishpc_debug() - * - * Controls debug output if enabled. - */ -static void -pcishpc_debug(char *fmt, ...) -{ - va_list ap; - - if (pcishpc_debug_enabled) { - va_start(ap, fmt); - vcmn_err(CE_WARN, fmt, ap); - va_end(ap); - } -} - - -/* - * pcishpc_dump_regs() - * - * Dumps all of the SHPC controller registers. - */ -static void -pcishpc_dump_regs(pcishpc_ctrl_t *ctrl_p) -{ - int slot, numSlots; - uint32_t reg; - char *state; - - cmn_err(CE_WARN, "pcishpc_dump_regs() called:"); - cmn_err(CE_WARN, "================================================" - "=========="); - - cmn_err(CE_WARN, "SHPC Base Offset " - ": 0x%08x", pcishpc_read_reg(ctrl_p, SHPC_BASE_OFFSET_REG)); - - reg = pcishpc_read_reg(ctrl_p, SHPC_SLOTS_AVAIL_I_REG); - - cmn_err(CE_WARN, "Number of PCIX slots avail (33 Mhz) : %d", - (reg & 31)); - - cmn_err(CE_WARN, "Number of PCIX slots avail (66 Mhz) : %d", - ((reg>>8) & 31)); - - cmn_err(CE_WARN, "Number of PCIX slots avail (100 Mhz) : %d", - ((reg>>16) & 31)); - - cmn_err(CE_WARN, "Number of PCIX slots avail (133 Mhz) : %d", - ((reg>>24) & 31)); - - reg = pcishpc_read_reg(ctrl_p, SHPC_SLOTS_AVAIL_II_REG); - - cmn_err(CE_WARN, "Number of conventional PCI slots (66 Mhz) : %d", - (reg & 31)); - - reg = pcishpc_read_reg(ctrl_p, SHPC_SLOT_CONFIGURATION_REG); - - numSlots = (reg & 31); - - cmn_err(CE_WARN, "Number of Slots connected to this port : %d", - numSlots); - - cmn_err(CE_WARN, "PCI Device # for First HotPlug Slot : %d", - ((reg>>8) & 31)); - - cmn_err(CE_WARN, "Physical Slot # for First PCI Device # : %d", - ((reg>>16) & 0x7ff)); - - cmn_err(CE_WARN, "Physical Slot Number Up/Down " - ": %d", ((reg>>29) & 0x1)); - - cmn_err(CE_WARN, "MRL Sensor Implemented " - ": %s", (reg & SHPC_SLOT_CONFIG_MRL_SENSOR) ? "Yes" : - "No"); - - cmn_err(CE_WARN, "Attention Button Implemented " - ": %s", (reg & SHPC_SLOT_CONFIG_ATTN_BUTTON) ? "Yes" : - "No"); - - reg = pcishpc_read_reg(ctrl_p, SHPC_PROF_IF_SBCR_REG); - - switch (reg & 7) { - case 0: - state = "33Mhz Conventional PCI"; - break; - case 1: - state = "66Mhz Conventional PCI"; - break; - case 2: - state = "66Mhz PCI-X"; - break; - case 3: - state = "100Mhz PCI-X"; - break; - case 4: - state = "133Mhz PCI-X"; - break; - default: - state = "Reserved (Error)"; - break; - } - - cmn_err(CE_WARN, "Current Port Operation Mode " - ": %s", state); - - cmn_err(CE_WARN, "SHPC Interrupt Message Number " - ": %d", ((reg>>16) &31)); - - cmn_err(CE_WARN, "SHPC Programming Interface " - ": %d", ((reg>>24) & 0xff)); - - reg = pcishpc_read_reg(ctrl_p, SHPC_COMMAND_STATUS_REG); - - cmn_err(CE_WARN, "SHPC Command Code " - ": %d", (reg & 0xff)); - - cmn_err(CE_WARN, "SHPC Target Slot " - ": %d", ((reg>>8) & 31)); - - cmn_err(CE_WARN, "SHPC Controller Busy " - ": %s", ((reg>>16) & 1) ? "Yes" : "No"); - - cmn_err(CE_WARN, "SHPC Controller Err: MRL Sensor " - ": %s", ((reg>>17) & 1) ? "Yes" : "No"); - - cmn_err(CE_WARN, "SHPC Controller Err: Invalid Command : %s", - ((reg>>18) & 1) ? "Yes" : "No"); - - cmn_err(CE_WARN, "SHPC Controller Err: Invalid Speed/Mode : %s", - ((reg>>19) & 1) ? "Yes" : "No"); - - reg = pcishpc_read_reg(ctrl_p, SHPC_IRQ_LOCATOR_REG); - - cmn_err(CE_WARN, "Command Completion Interrupt Pending : %s", - (reg & SHPC_IRQ_CMD_COMPLETE) ? "Yes" : "No"); - - for (slot = 0; slot < numSlots; slot++) { - cmn_err(CE_WARN, "Slot %d Interrupt Pending " - ": %s", slot+1, - (reg & (SHPC_IRQ_SLOT_N_PENDING<<slot)) ? "Yes" : "No"); - } - - reg = pcishpc_read_reg(ctrl_p, SHPC_SERR_LOCATOR_REG); - - cmn_err(CE_WARN, "Arbiter SERR Pending " - ": %s", (reg & SHPC_IRQ_SERR_ARBITER_PENDING) ? - "Yes" : "No"); - - for (slot = 0; slot < numSlots; slot++) { - cmn_err(CE_WARN, "Slot %d SERR Pending " - ": %s", slot+1, (reg & - (SHPC_IRQ_SERR_SLOT_N_PENDING<<slot)) ? - "Yes" : "No"); - } - - reg = pcishpc_read_reg(ctrl_p, SHPC_CTRL_SERR_INT_REG); - - cmn_err(CE_WARN, "Global Interrupt Mask " - ": %s", (reg & SHPC_SERR_INT_GLOBAL_IRQ_MASK) ? - "Yes" : "No"); - - cmn_err(CE_WARN, "Global SERR Mask " - ": %s", (reg & SHPC_SERR_INT_GLOBAL_SERR_MASK) ? - "Yes" : "No"); - - cmn_err(CE_WARN, "Command Completion Interrupt Mask " - ": %s", (reg & SHPC_SERR_INT_CMD_COMPLETE_MASK) ? - "Yes" : "No"); - - cmn_err(CE_WARN, "Arbiter SERR Mask " - ": %s", (reg & SHPC_SERR_INT_ARBITER_SERR_MASK) ? - "Yes" : "No"); - - cmn_err(CE_WARN, "Command Completion Detected " - ": %s", (reg & SHPC_SERR_INT_CMD_COMPLETE_IRQ) ? - "Yes" : "No"); - - cmn_err(CE_WARN, "Arbiter Timeout Detected " - ": %s", (reg & SHPC_SERR_INT_ARBITER_IRQ) ? - "Yes" : "No"); - - - for (slot = 0; slot < numSlots; slot++) { - cmn_err(CE_WARN, "Logical Slot %d Registers:", slot+1); - cmn_err(CE_WARN, "------------------------------------"); - - reg = pcishpc_read_reg(ctrl_p, SHPC_LOGICAL_SLOT_REGS+slot); - - cmn_err(CE_WARN, "Slot %d state " - ": %s", slot+1, - pcishpc_textslotstate(pcishpc_slot_shpc_to_hpc( - (reg & 3)))); - - cmn_err(CE_WARN, "Slot %d Power Indicator State " - ": %s", slot+1, - pcishpc_textledstate(pcishpc_led_shpc_to_hpc( - (reg>>2) &3))); - - cmn_err(CE_WARN, "Slot %d Attention Indicator State " - ": %s", slot+1, - pcishpc_textledstate(pcishpc_led_shpc_to_hpc( - (reg>>4)&3))); - - cmn_err(CE_WARN, "Slot %d Power Fault " - ": %s", slot+1, ((reg>>6)&1) ? "Fault Detected" : - "No Fault"); - cmn_err(CE_WARN, "Slot %d Attention Button " - ": %s", slot+1, ((reg>>7)&1) ? "Depressed" : - "Not Depressed"); - cmn_err(CE_WARN, "Slot %d MRL Sensor " - ": %s", slot+1, ((reg>>8)&1) ? "Not Closed" : - "Closed"); - cmn_err(CE_WARN, "Slot %d 66mhz Capable " - ": %s", slot+1, ((reg>>9)&1) ? "66mhz" : "33mgz"); - - switch ((reg>>10)&3) { - case 0: - state = "Card Present 7.5W"; - break; - case 1: - state = "Card Present 15W"; - break; - case 2: - state = "Card Present 25W"; - break; - case 3: - state = "Slot Empty"; - break; - } - - cmn_err(CE_WARN, "Slot %d PRSNT1#/PRSNT2# " - ": %s", slot+1, state); - - switch ((reg>>12)&3) { - case 0: - state = "Non PCI-X"; - break; - case 1: - state = "66mhz PCI-X"; - break; - case 2: - state = "Reserved"; - break; - case 3: - state = "133mhz PCI-X"; - break; - } - - cmn_err(CE_WARN, "Slot %d Card Presence Change Detected : %s", - slot+1, (reg & SHPC_SLOT_PRESENCE_DETECTED) ? "Yes" : - "No"); - cmn_err(CE_WARN, "Slot %d Isolated Power Fault Detected : %s", - slot+1, (reg & SHPC_SLOT_ISO_PWR_DETECTED) ? "Yes" : - "No"); - cmn_err(CE_WARN, "Slot %d Attention Button Press Detected" - ": %s", slot+1, - (reg & SHPC_SLOT_ATTN_DETECTED) ? "Yes" : "No"); - cmn_err(CE_WARN, "Slot %d MRL Sensor Change Detected " - ": %s", slot+1, - (reg & SHPC_SLOT_MRL_DETECTED) ? "Yes" : "No"); - cmn_err(CE_WARN, "Slot %d Connected Power Fault Detected" - ": %s", slot+1, - (reg & SHPC_SLOT_POWER_DETECTED) ? "Yes" : "No"); - - cmn_err(CE_WARN, "Slot %d Card Presence IRQ Masked " - ": %s", slot+1, - (reg & SHPC_SLOT_PRESENCE_MASK) ? "Yes" : "No"); - cmn_err(CE_WARN, "Slot %d Isolated Power Fault IRQ Masked" - ": %s", slot+1, - (reg & SHPC_SLOT_ISO_PWR_MASK) ? "Yes" : "No"); - cmn_err(CE_WARN, "Slot %d Attention Button IRQ Masked " - ": %s", slot+1, (reg & SHPC_SLOT_ATTN_MASK) ? "Yes" : - "No"); - cmn_err(CE_WARN, "Slot %d MRL Sensor IRQ Masked " - ": %s", slot+1, - (reg & SHPC_SLOT_MRL_MASK) ? "Yes" : "No"); - cmn_err(CE_WARN, "Slot %d Connected Power Fault IRQ Masked" - " : %s", slot+1, - (reg & SHPC_SLOT_POWER_MASK) ? "Yes" : "No"); - cmn_err(CE_WARN, "Slot %d MRL Sensor SERR Masked " - ": %s", slot+1, - (reg & SHPC_SLOT_MRL_SERR_MASK) ? "Yes" : "No"); - cmn_err(CE_WARN, "Slot %d Connected Power Fault SERR Masked :" - "%s", slot+1, - (reg & SHPC_SLOT_POWER_SERR_MASK) ? "Yes" : "No"); - } -} - -static void -pcishpc_attn_btn_handler(pcishpc_t *pcishpc_p) -{ - hpc_led_state_t power_led_state; - callb_cpr_t cprinfo; - - pcishpc_debug("pcishpc_attn_btn_handler: thread started\n"); - - CALLB_CPR_INIT(&cprinfo, &pcishpc_p->ctrl->shpc_mutex, - callb_generic_cpr, "pcishpc_attn_btn_handler"); - - mutex_enter(&pcishpc_p->ctrl->shpc_mutex); - - /* wait for ATTN button event */ - cv_wait(&pcishpc_p->attn_btn_cv, &pcishpc_p->ctrl->shpc_mutex); - - while (pcishpc_p->attn_btn_thread_exit == B_FALSE) { - if (pcishpc_p->attn_btn_pending == B_TRUE) { - /* get the current state of power LED */ - power_led_state = pcishpc_p->power_led_state; - - /* Blink the Power LED while we wait for 5 seconds */ - (void) pcishpc_setled(pcishpc_p, HPC_POWER_LED, - HPC_LED_BLINK); - - /* wait for 5 seconds before taking any action */ - if (cv_timedwait(&pcishpc_p->attn_btn_cv, - &pcishpc_p->ctrl->shpc_mutex, - ddi_get_lbolt() + SEC_TO_TICK(5)) == -1) { - /* - * It is a time out; - * make sure the ATTN pending flag is - * still ON before sending the event - * to HPS framework. - */ - if (pcishpc_p->attn_btn_pending == B_TRUE) { - /* - * send the ATTN button event - * to HPS framework - */ - pcishpc_p->attn_btn_pending = B_FALSE; - (void) hpc_slot_event_notify( - pcishpc_p->slot_handle, - HPC_EVENT_SLOT_ATTN, - HPC_EVENT_NORMAL); - } - } - - /* restore the power LED state ??? XXX */ - (void) pcishpc_setled(pcishpc_p, HPC_POWER_LED, - power_led_state); - continue; - } - - /* wait for another ATTN button event */ - cv_wait(&pcishpc_p->attn_btn_cv, &pcishpc_p->ctrl->shpc_mutex); - } - - pcishpc_debug("pcishpc_attn_btn_handler: thread exit\n"); - cv_signal(&pcishpc_p->attn_btn_cv); - CALLB_CPR_EXIT(&cprinfo); - thread_exit(); -} - -/* - * setup slot name/slot-number info. - */ -static void -pcishpc_set_slot_name(pcishpc_ctrl_t *ctrl_p, int slot) -{ - pcishpc_t *p = ctrl_p->slots[slot]; - uchar_t *slotname_data; - int *slotnum; - uint_t count; - int len; - uchar_t *s; - uint32_t bit_mask; - int pci_id_cnt, pci_id_bit; - int slots_before, found; - int invalid_slotnum = 0; - - if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ctrl_p->shpc_dip, - DDI_PROP_DONTPASS, "physical-slot#", &slotnum, &count) == - DDI_PROP_SUCCESS) { - p->phy_slot_num = slotnum[0]; - ddi_prop_free(slotnum); - } else { - if (ctrl_p->deviceIncreases) - p->phy_slot_num = ctrl_p->physStart + slot; - else - p->phy_slot_num = ctrl_p->physStart - slot; - - if ((ndi_prop_update_int(DDI_DEV_T_NONE, ctrl_p->shpc_dip, - "physical-slot#", p->phy_slot_num)) != DDI_SUCCESS) { - pcishpc_debug("pcishpc_set_slot_name(): failed to " - "create phyical-slot#%d", p->phy_slot_num); - } - } - - if (!p->phy_slot_num) { /* platform may not have initialized it */ - p->phy_slot_num = pci_config_get8(ctrl_p->shpc_config_hdl, - PCI_BCNF_SECBUS); - invalid_slotnum = 1; - } - - /* - * construct the slot_name: - * if "slot-names" property exists then use that name - * else if valid slot number exists then it is "pci<slot-num>". - * else it will be "pci<sec-bus-number>dev<dev-number>" - */ - if (ddi_getlongprop(DDI_DEV_T_ANY, ctrl_p->shpc_dip, DDI_PROP_DONTPASS, - "slot-names", (caddr_t)&slotname_data, - &len) == DDI_PROP_SUCCESS) { - - bit_mask = slotname_data[3] | (slotname_data[2] << 8) | - (slotname_data[1] << 16) | (slotname_data[0] << 24); - - pci_id_bit = 1; - pci_id_cnt = slots_before = found = 0; - - /* - * Walk the bit mask until we find the bit that corresponds - * to our slots device number. We count how many bits - * we find before we find our slot's bit. - */ - while (!found && (pci_id_cnt < 32)) { - - while (p->deviceNum != pci_id_cnt) { - - /* - * Find the next bit set. - */ - while (!(bit_mask & pci_id_bit) && - (pci_id_cnt < 32)) { - pci_id_bit = pci_id_bit << 1; - pci_id_cnt++; - } - - if (p->deviceNum != pci_id_cnt) - slots_before++; - else - found = 1; - } - } - - if (pci_id_cnt < 32) { - - /* - * Set ptr to first string. - */ - s = slotname_data + 4; - - /* - * Increment past all the strings for the slots - * before ours. - */ - while (slots_before) { - while (*s != NULL) - s++; - s++; - slots_before--; - } - - (void) sprintf(p->slot_info.pci_slot_name, (char *)s); - - kmem_free(slotname_data, len); - return; - } - - /* slot-names entry not found */ - pcishpc_debug("pcishpc_set_slot_name(): " - "No slot-names entry found for slot #%d", - p->phy_slot_num); - kmem_free(slotname_data, len); - } - - if (invalid_slotnum) - (void) sprintf(p->slot_info.pci_slot_name, "pci%d", - p->deviceNum); - else - (void) sprintf(p->slot_info.pci_slot_name, "pci%d", - p->phy_slot_num); -} diff --git a/usr/src/uts/common/io/pciex/hotplug/pcie_hp.c b/usr/src/uts/common/io/pciex/hotplug/pcie_hp.c new file mode 100644 index 0000000000..55cc077a5b --- /dev/null +++ b/usr/src/uts/common/io/pciex/hotplug/pcie_hp.c @@ -0,0 +1,1300 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * This file contains the common hotplug code that is used by Standard + * PCIe and PCI HotPlug Controller code. + * + * NOTE: This file is compiled and delivered through misc/pcie module. + */ + +#include <sys/types.h> +#include <sys/conf.h> +#include <sys/kmem.h> +#include <sys/debug.h> +#include <sys/vtrace.h> +#include <sys/autoconf.h> +#include <sys/varargs.h> +#include <sys/ddi_impldefs.h> +#include <sys/time.h> +#include <sys/note.h> +#include <sys/callb.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/sysevent.h> +#include <sys/sysevent/eventdefs.h> +#include <sys/sysevent/dr.h> +#include <sys/pci_impl.h> +#include <sys/pci_cap.h> +#include <sys/hotplug/pci/pcicfg.h> +#include <sys/hotplug/pci/pcie_hp.h> +#include <sys/hotplug/pci/pciehpc.h> +#include <sys/hotplug/pci/pcishpc.h> +#include <io/pciex/pcieb.h> + +/* Local functions prototype */ +static int pcie_hp_list_occupants(dev_info_t *dip, void *arg); +static int pcie_hp_register_port(dev_info_t *dip, dev_info_t *pdip, + char *cn_name); +static int pcie_hp_register_ports_for_dev(dev_info_t *dip, int device_num); +static int pcie_hp_unregister_ports_cb(ddi_hp_cn_info_t *info, void *arg); +static int pcie_hp_get_port_state(ddi_hp_cn_info_t *info, void *arg); +static int pcie_hp_match_dev_func(dev_info_t *dip, void *hdl); +static boolean_t pcie_hp_match_dev(dev_info_t *dip, int dev_num); +static int pcie_hp_get_df_from_port_name(char *cn_name, int *dev_num, + int *func_num); +static int pcie_hp_create_port_name_num(dev_info_t *dip, + ddi_hp_cn_info_t *cn_info); +static int pcie_hp_check_hardware_existence(dev_info_t *dip, int dev_num, + int func_num); + +/* + * Global functions (called by other drivers/modules) + */ + +/* + * return description text for led state + */ +char * +pcie_led_state_text(pcie_hp_led_state_t state) +{ + switch (state) { + case PCIE_HP_LED_ON: + return (PCIEHPC_PROP_VALUE_ON); + case PCIE_HP_LED_OFF: + return (PCIEHPC_PROP_VALUE_OFF); + case PCIE_HP_LED_BLINK: + default: + return (PCIEHPC_PROP_VALUE_BLINK); + } +} + +/* + * return description text for slot condition + */ +char * +pcie_slot_condition_text(ap_condition_t condition) +{ + switch (condition) { + case AP_COND_UNKNOWN: + return (PCIEHPC_PROP_VALUE_UNKNOWN); + case AP_COND_OK: + return (PCIEHPC_PROP_VALUE_OK); + case AP_COND_FAILING: + return (PCIEHPC_PROP_VALUE_FAILING); + case AP_COND_FAILED: + return (PCIEHPC_PROP_VALUE_FAILED); + case AP_COND_UNUSABLE: + return (PCIEHPC_PROP_VALUE_UNUSABLE); + default: + return (PCIEHPC_PROP_VALUE_UNKNOWN); + } +} + +/* + * routine to copy in a nvlist from userland + */ +int +pcie_copyin_nvlist(char *packed_buf, size_t packed_sz, nvlist_t **nvlp) +{ + int ret = DDI_SUCCESS; + char *packed; + nvlist_t *dest = NULL; + + if (packed_buf == NULL || packed_sz == 0) + return (DDI_EINVAL); + + /* copyin packed nvlist */ + if ((packed = kmem_alloc(packed_sz, KM_SLEEP)) == NULL) + return (DDI_ENOMEM); + + if (copyin(packed_buf, packed, packed_sz) != 0) { + cmn_err(CE_WARN, "pcie_copyin_nvlist: copyin failed.\n"); + ret = DDI_FAILURE; + goto copyin_cleanup; + } + + /* unpack packed nvlist */ + if ((ret = nvlist_unpack(packed, packed_sz, &dest, KM_SLEEP)) != 0) { + cmn_err(CE_WARN, "pcie_copyin_nvlist: nvlist_unpack " + "failed with err %d\n", ret); + switch (ret) { + case EINVAL: + case ENOTSUP: + ret = DDI_EINVAL; + goto copyin_cleanup; + case ENOMEM: + ret = DDI_ENOMEM; + goto copyin_cleanup; + default: + ret = DDI_FAILURE; + goto copyin_cleanup; + } + } + *nvlp = dest; +copyin_cleanup: + kmem_free(packed, packed_sz); + return (ret); +} + +/* + * routine to copy out a nvlist to userland + */ +int +pcie_copyout_nvlist(nvlist_t *nvl, char *packed_buf, size_t *buf_sz) +{ + int err = 0; + char *buf = NULL; + size_t packed_sz; + + if (nvl == NULL || packed_buf == NULL || buf_sz == NULL) + return (DDI_EINVAL); + + /* pack nvlist, the library will allocate memory */ + if ((err = nvlist_pack(nvl, &buf, &packed_sz, NV_ENCODE_NATIVE, 0)) + != 0) { + cmn_err(CE_WARN, "pcie_copyout_nvlist: nvlist_pack " + "failed with err %d\n", err); + switch (err) { + case EINVAL: + case ENOTSUP: + return (DDI_EINVAL); + case ENOMEM: + return (DDI_ENOMEM); + default: + return (DDI_FAILURE); + } + } + if (packed_sz > *buf_sz) { + return (DDI_EINVAL); + } + + /* copyout packed nvlist */ + if (copyout(buf, packed_buf, packed_sz) != 0) { + cmn_err(CE_WARN, "pcie_copyout_nvlist: copyout " "failed.\n"); + kmem_free(buf, packed_sz); + return (DDI_FAILURE); + } + + *buf_sz = packed_sz; + kmem_free(buf, packed_sz); + return (DDI_SUCCESS); +} + +/* + * init bus_hp_op entry and init hotpluggable slots & virtual ports + */ +int +pcie_hp_init(dev_info_t *dip, caddr_t arg) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + int ret = DDI_SUCCESS, count; + dev_info_t *cdip; + + if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) { + /* Init hotplug controller */ + ret = pciehpc_init(dip, arg); + } else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) { + ret = pcishpc_init(dip); + } + + if (ret != DDI_SUCCESS) { + cmn_err(CE_WARN, "pcie_hp_init: initialize hotplug " + "controller failed with %d\n", ret); + return (ret); + } + + ndi_devi_enter(dip, &count); + + /* Create port for the first level children */ + cdip = ddi_get_child(dip); + while (cdip != NULL) { + if ((ret = pcie_hp_register_port(cdip, dip, NULL)) + != DDI_SUCCESS) { + /* stop and cleanup */ + break; + } + cdip = ddi_get_next_sibling(cdip); + } + ndi_devi_exit(dip, count); + if (ret != DDI_SUCCESS) { + cmn_err(CE_WARN, "pcie_hp_init: initialize virtual " + "hotplug port failed with %d\n", ret); + (void) pcie_hp_uninit(dip); + + return (ret); + } + + return (DDI_SUCCESS); +} + +/* + * uninit the hotpluggable slots and virtual ports + */ +int +pcie_hp_uninit(dev_info_t *dip) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + pcie_hp_unreg_port_t arg; + + /* + * Must set arg.rv to NDI_SUCCESS so that if there's no port + * under this dip, we still return success thus the bridge + * driver can be successfully detached. + * + * Note that during the probe PCI configurator calls + * ndi_devi_offline() to detach driver for a new probed bridge, + * so that it can reprogram the resources for the bridge, + * ndi_devi_offline() calls into pcieb_detach() which in turn + * calls into this function. In this case there are no ports + * created under a new probe bridge dip, as ports are only + * created after the configurator finishing probing, thus the + * ndi_hp_walk_cn() will see no ports when this is called + * from the PCI configurtor. + */ + arg.nexus_dip = dip; + arg.connector_num = DDI_HP_CN_NUM_NONE; + arg.rv = NDI_SUCCESS; + + /* tear down all virtual hotplug handles */ + ndi_hp_walk_cn(dip, pcie_hp_unregister_ports_cb, &arg); + + if (arg.rv != NDI_SUCCESS) + return (DDI_FAILURE); + + if (PCIE_IS_PCIE_HOTPLUG_ENABLED(bus_p)) + (void) pciehpc_uninit(dip); + else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p)) + (void) pcishpc_uninit(dip); + + return (DDI_SUCCESS); +} + +/* + * interrupt handler + */ +int +pcie_hp_intr(dev_info_t *dip) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + int ret = DDI_INTR_UNCLAIMED; + + if (PCIE_IS_PCIE_HOTPLUG_ENABLED(bus_p)) + ret = pciehpc_intr(dip); + else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p)) + ret = pcishpc_intr(dip); + + return (ret); +} + +/* + * Probe the given PCIe/PCI Hotplug Connection (CN). + */ +/*ARGSUSED*/ +int +pcie_hp_probe(pcie_hp_slot_t *slot_p) +{ + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + dev_info_t *dip = ctrl_p->hc_dip; + + /* + * Call the configurator to probe a given PCI hotplug + * Hotplug Connection (CN). + */ + if (pcicfg_configure(dip, slot_p->hs_device_num, PCICFG_ALL_FUNC, 0) + != PCICFG_SUCCESS) { + PCIE_DBG("pcie_hp_probe() failed\n"); + return (DDI_FAILURE); + } + slot_p->hs_condition = AP_COND_OK; + pcie_hp_create_occupant_props(dip, makedevice(ddi_driver_major(dip), + slot_p->hs_minor), slot_p->hs_device_num); + + /* + * Create ports for the newly probed devices. + * Note, this is only for the first level children because the + * descendants' ports will be created during bridge driver attach. + */ + return (pcie_hp_register_ports_for_dev(dip, slot_p->hs_device_num)); +} + +/* + * Unprobe the given PCIe/PCI Hotplug Connection (CN): + * 1. remove all child device nodes + * 2. unregister all dependent ports + */ +/*ARGSUSED*/ +int +pcie_hp_unprobe(pcie_hp_slot_t *slot_p) +{ + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + dev_info_t *dip = ctrl_p->hc_dip; + pcie_hp_unreg_port_t arg; + + /* + * Call the configurator to unprobe a given PCI hotplug + * Hotplug Connection (CN). + */ + if (pcicfg_unconfigure(dip, slot_p->hs_device_num, PCICFG_ALL_FUNC, 0) + != PCICFG_SUCCESS) { + PCIE_DBG("pcie_hp_unprobe() failed\n"); + return (DDI_FAILURE); + } + slot_p->hs_condition = AP_COND_UNKNOWN; + pcie_hp_delete_occupant_props(dip, makedevice(ddi_driver_major(dip), + slot_p->hs_minor)); + + /* + * Remove ports for the unprobed devices. + * Note, this is only for the first level children because the + * descendants' ports were already removed during bridge driver dettach. + */ + arg.nexus_dip = dip; + arg.connector_num = slot_p->hs_info.cn_num; + arg.rv = NDI_SUCCESS; + ndi_hp_walk_cn(dip, pcie_hp_unregister_ports_cb, &arg); + + return (arg.rv == NDI_SUCCESS) ? (DDI_SUCCESS) : (DDI_FAILURE); +} + +/* Read-only probe: no hardware register programming. */ +int +pcie_read_only_probe(dev_info_t *dip, char *cn_name, dev_info_t **pcdip) +{ + long dev, func; + int ret; + char *sp; + dev_info_t *cdip; + + *pcdip = NULL; + /* + * Parse the string of a pci Port name and get the device number + * and function number. + */ + if (ddi_strtol(cn_name + 4, &sp, 10, &dev) != 0) + return (DDI_EINVAL); + if (ddi_strtol(sp + 1, NULL, 10, &func) != 0) + return (DDI_EINVAL); + + ret = pcicfg_configure(dip, (int)dev, (int)func, + PCICFG_FLAG_READ_ONLY); + if (ret == PCICFG_SUCCESS) { + cdip = pcie_hp_devi_find(dip, (int)dev, (int)func); + *pcdip = cdip; + } + return (ret); +} + +/* Read-only unprobe: no hardware register programming. */ +int +pcie_read_only_unprobe(dev_info_t *dip, char *cn_name) +{ + long dev, func; + int ret; + char *sp; + + /* + * Parse the string of a pci Port name and get the device number + * and function number. + */ + if (ddi_strtol(cn_name + 4, &sp, 10, &dev) != 0) + return (DDI_EINVAL); + if (ddi_strtol(sp + 1, NULL, 10, &func) != 0) + return (DDI_EINVAL); + + ret = pcicfg_unconfigure(dip, (int)dev, (int)func, + PCICFG_FLAG_READ_ONLY); + + return (ret); +} + +/* Control structure used to find a device in the devinfo tree */ +struct pcie_hp_find_ctrl { + uint_t device; + uint_t function; + dev_info_t *dip; +}; + +/* + * find a devinfo node with specified device and function number + * in the device tree under 'dip' + */ +dev_info_t * +pcie_hp_devi_find(dev_info_t *dip, uint_t device, uint_t function) +{ + struct pcie_hp_find_ctrl ctrl; + int count; + + ctrl.device = device; + ctrl.function = function; + ctrl.dip = NULL; + + ndi_devi_enter(dip, &count); + ddi_walk_devs(ddi_get_child(dip), pcie_hp_match_dev_func, + (void *)&ctrl); + ndi_devi_exit(dip, count); + + return (ctrl.dip); +} + +/* + * routine to create 'pci-occupant' property for a hotplug slot + */ +void +pcie_hp_create_occupant_props(dev_info_t *dip, dev_t dev, int pci_dev) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + pcie_hp_ctrl_t *ctrl_p = (pcie_hp_ctrl_t *)bus_p->bus_hp_ctrl; + pcie_hp_slot_t *slotp; + pcie_hp_cn_cfg_t cn_cfg; + pcie_hp_occupant_info_t *occupant; + int circular, i; + + ndi_devi_enter(dip, &circular); + + if (PCIE_IS_PCIE_HOTPLUG_ENABLED(bus_p)) { + slotp = (ctrl_p && (pci_dev == 0)) ? + ctrl_p->hc_slots[pci_dev] : NULL; + } else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p)) { + if (ctrl_p) { + int slot_num; + + slot_num = (ctrl_p->hc_device_increases) ? + (pci_dev - ctrl_p->hc_device_start) : + (pci_dev + ctrl_p->hc_device_start); + + slotp = ctrl_p->hc_slots[slot_num]; + } else { + slotp = NULL; + } + } + + if (slotp == NULL) + return; + + occupant = kmem_alloc(sizeof (pcie_hp_occupant_info_t), KM_SLEEP); + occupant->i = 0; + + cn_cfg.flag = B_FALSE; + cn_cfg.rv = NDI_SUCCESS; + cn_cfg.dip = NULL; + cn_cfg.slotp = (void *)slotp; + cn_cfg.cn_private = (void *)occupant; + + ddi_walk_devs(ddi_get_child(dip), pcie_hp_list_occupants, + (void *)&cn_cfg); + + if (occupant->i == 0) { + /* no occupants right now, need to create stub property */ + char *c[] = { "" }; + (void) ddi_prop_update_string_array(dev, dip, "pci-occupant", + c, 1); + } else { + (void) ddi_prop_update_string_array(dev, dip, "pci-occupant", + occupant->id, occupant->i); + } + + for (i = 0; i < occupant->i; i++) + kmem_free(occupant->id[i], sizeof (char[MAXPATHLEN])); + + kmem_free(occupant, sizeof (pcie_hp_occupant_info_t)); + + ndi_devi_exit(dip, circular); +} + +/* + * routine to remove 'pci-occupant' property for a hotplug slot + */ +void +pcie_hp_delete_occupant_props(dev_info_t *dip, dev_t dev) +{ + (void) ddi_prop_remove(dev, dip, "pci-occupant"); +} + +/* + * general code to create a minor node, called from hotplug controller + * drivers. + */ +int +pcie_create_minor_node(pcie_hp_ctrl_t *ctrl_p, int slot) +{ + dev_info_t *dip = ctrl_p->hc_dip; + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[slot]; + ddi_hp_cn_info_t *info_p = &slot_p->hs_info; + + if (ddi_create_minor_node(dip, info_p->cn_name, + S_IFCHR, slot_p->hs_minor, + DDI_NT_PCI_ATTACHMENT_POINT, 0) != DDI_SUCCESS) { + return (DDI_FAILURE); + } + + (void) ddi_prop_update_int(DDI_DEV_T_NONE, + dip, "ap-names", 1 << slot_p->hs_device_num); + + return (DDI_SUCCESS); +} + +/* + * general code to remove a minor node, called from hotplug controller + * drivers. + */ +void +pcie_remove_minor_node(pcie_hp_ctrl_t *ctrl_p, int slot) +{ + ddi_remove_minor_node(ctrl_p->hc_dip, + ctrl_p->hc_slots[slot]->hs_info.cn_name); +} + +/* + * Local functions (called within this file) + */ + +/* + * Register ports for all the children with device number device_num + */ +static int +pcie_hp_register_ports_for_dev(dev_info_t *dip, int device_num) +{ + dev_info_t *cdip; + int rv; + + for (cdip = ddi_get_child(dip); cdip; + cdip = ddi_get_next_sibling(cdip)) { + if (pcie_hp_match_dev(cdip, device_num)) { + /* + * Found the newly probed device under the + * current slot. Register a port for it. + */ + if ((rv = pcie_hp_register_port(cdip, dip, NULL)) + != DDI_SUCCESS) + return (rv); + } else { + continue; + } + } + + return (DDI_SUCCESS); +} + +/* + * Unregister ports of a pci bridge dip, get called from ndi_hp_walk_cn() + * + * If connector_num is specified, then unregister the slot's dependent ports + * only; Otherwise, unregister all ports of a pci bridge dip. + */ +static int +pcie_hp_unregister_ports_cb(ddi_hp_cn_info_t *info, void *arg) +{ + pcie_hp_unreg_port_t *unreg_arg = (pcie_hp_unreg_port_t *)arg; + dev_info_t *dip = unreg_arg->nexus_dip; + int rv = NDI_SUCCESS; + + if (info->cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT) { + unreg_arg->rv = rv; + return (DDI_WALK_CONTINUE); + } + + if (unreg_arg->connector_num != DDI_HP_CN_NUM_NONE) { + /* Unregister ports for all unprobed devices under a slot. */ + if (unreg_arg->connector_num == info->cn_num_dpd_on) { + + rv = ndi_hp_unregister(dip, info->cn_name); + } + } else { + + /* Unregister all ports of a pci bridge dip. */ + rv = ndi_hp_unregister(dip, info->cn_name); + } + + unreg_arg->rv = rv; + if (rv == NDI_SUCCESS) + return (DDI_WALK_CONTINUE); + else + return (DDI_WALK_TERMINATE); +} + +/* + * Find a port according to cn_name and get the port's state. + */ +static int +pcie_hp_get_port_state(ddi_hp_cn_info_t *info, void *arg) +{ + pcie_hp_port_state_t *port = (pcie_hp_port_state_t *)arg; + + if (info->cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT) + return (DDI_WALK_CONTINUE); + + if (strcmp(info->cn_name, port->cn_name) == 0) { + /* Matched. */ + port->cn_state = info->cn_state; + port->rv = DDI_SUCCESS; + + return (DDI_WALK_TERMINATE); + } + + return (DDI_WALK_CONTINUE); +} + +/* + * Find the physical slot with the given device number; + * return the slot if found. + */ +static pcie_hp_slot_t * +pcie_find_physical_slot(dev_info_t *dip, int dev_num) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + pcie_hp_ctrl_t *ctrl = PCIE_GET_HP_CTRL(dip); + + if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) { + /* PCIe has only one slot */ + return (dev_num == 0) ? (ctrl->hc_slots[0]) : (NULL); + } else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) { + for (int slot = 0; slot < ctrl->hc_num_slots_impl; slot++) { + if (ctrl->hc_slots[slot]->hs_device_num == dev_num) { + /* found */ + return (ctrl->hc_slots[slot]); + } + } + } + + return (NULL); +} + +/* + * setup slot name/slot-number info for the port which is being registered. + */ +static int +pcie_hp_create_port_name_num(dev_info_t *dip, ddi_hp_cn_info_t *cn_info) +{ + int ret, dev_num, func_num, name_len; + dev_info_t *pdip = ddi_get_parent(dip); + pcie_bus_t *bus_p = PCIE_DIP2BUS(pdip); + pcie_hp_slot_t *slot; + pcie_req_id_t bdf; + char tmp[PCIE_HP_DEV_FUNC_NUM_STRING_LEN]; + + ret = pcie_get_bdf_from_dip(dip, &bdf); + if (ret != DDI_SUCCESS) { + return (ret); + } + if (PCIE_IS_RP(bus_p) || PCIE_IS_SWD(bus_p) || + PCIE_IS_PCI2PCIE(bus_p)) { + /* + * It is under a PCIe device, devcie number is always 0; + * function number might > 8 in ARI supported case. + */ + dev_num = 0; + func_num = (bdf & ((~PCI_REG_BUS_M) >> 8)); + } else { + dev_num = (bdf & (PCI_REG_DEV_M >> 8)) >> 3; + func_num = bdf & (PCI_REG_FUNC_M >> 8); + } + /* + * The string length of dev_num and func_num must be no longer than 4 + * including the string end mark. (With ARI case considered, e.g., + * dev_num=0x0, func_num=0xff.) + */ + (void) snprintf(tmp, PCIE_HP_DEV_FUNC_NUM_STRING_LEN, "%x%x", + dev_num, func_num); + /* + * Calculate the length of cn_name. + * The format of pci port name is: pci.d,f + * d stands for dev_num, f stands for func_num. So the length of the + * name string can be calculated as following. + */ + name_len = strlen(tmp) + PCIE_HP_PORT_NAME_STRING_LEN + 1; + + cn_info->cn_name = (char *)kmem_zalloc(name_len, KM_SLEEP); + (void) snprintf(cn_info->cn_name, name_len, "pci.%x,%x", + dev_num, func_num); + cn_info->cn_num = (dev_num << 8) | func_num; + slot = pcie_find_physical_slot(pdip, dev_num); + + cn_info->cn_num_dpd_on = slot ? + slot->hs_info.cn_num : DDI_HP_CN_NUM_NONE; + + return (DDI_SUCCESS); +} + +/* + * Extract device and function number from port name, whose format is + * something like 'pci.1,0' + */ +static int +pcie_hp_get_df_from_port_name(char *cn_name, int *dev_num, int *func_num) +{ + int name_len, ret; + long d, f; + char *sp; + + /* some checks for the input name */ + name_len = strlen(cn_name); + if ((name_len <= PCIE_HP_PORT_NAME_STRING_LEN) || + (name_len > (PCIE_HP_PORT_NAME_STRING_LEN + + PCIE_HP_DEV_FUNC_NUM_STRING_LEN - 1)) || + (strncmp("pci.", cn_name, 4) != 0)) { + return (DDI_EINVAL); + } + ret = ddi_strtol(cn_name + 4, &sp, 10, &d); + if (ret != DDI_SUCCESS) + return (ret); + + if (strncmp(",", sp, 1) != 0) + return (DDI_EINVAL); + + ret = ddi_strtol(sp + 1, NULL, 10, &f); + if (ret != DDI_SUCCESS) + return (ret); + *dev_num = (int)d; + *func_num = (int)f; + + return (ret); +} + +/* + * Check/copy cn_name and set connection numbers. + * If it is a valid name, then setup cn_info for the newly created port. + */ +static int +pcie_hp_setup_port_name_num(dev_info_t *pdip, char *cn_name, + ddi_hp_cn_info_t *cn_info) +{ + int dev_num, func_num, ret; + pcie_hp_slot_t *slot; + + if ((ret = pcie_hp_get_df_from_port_name(cn_name, &dev_num, &func_num)) + != DDI_SUCCESS) + return (ret); + + if (pcie_hp_check_hardware_existence(pdip, dev_num, func_num) == + DDI_SUCCESS) { + cn_info->cn_state = DDI_HP_CN_STATE_PRESENT; + } else { + cn_info->cn_state = DDI_HP_CN_STATE_EMPTY; + } + + cn_info->cn_name = ddi_strdup(cn_name, KM_SLEEP); + cn_info->cn_num = (dev_num << 8) | func_num; + + slot = pcie_find_physical_slot(pdip, dev_num); + if (slot) { + cn_info->cn_num_dpd_on = slot->hs_info.cn_num; + } else { + cn_info->cn_num_dpd_on = DDI_HP_CN_NUM_NONE; + } + return (DDI_SUCCESS); +} + +static int +ndi2ddi(int n) +{ + int ret; + + switch (n) { + case NDI_SUCCESS: + ret = DDI_SUCCESS; + break; + case NDI_NOMEM: + ret = DDI_ENOMEM; + break; + case NDI_BUSY: + ret = DDI_EBUSY; + break; + case NDI_EINVAL: + ret = DDI_EINVAL; + break; + case NDI_ENOTSUP: + ret = DDI_ENOTSUP; + break; + case NDI_FAILURE: + default: + ret = DDI_FAILURE; + break; + } + return (ret); +} + +/* + * Common routine to create and register a new port + * + * Create an empty port if dip is NULL, and cn_name needs to be specified in + * this case. Otherwise, create a port mapping to the specified dip, and cn_name + * is not needed in this case. + */ +static int +pcie_hp_register_port(dev_info_t *dip, dev_info_t *pdip, char *cn_name) +{ + ddi_hp_cn_info_t *cn_info; + int ret; + + ASSERT((dip == NULL) != (cn_name == NULL)); + cn_info = kmem_zalloc(sizeof (ddi_hp_cn_info_t), KM_SLEEP); + if (dip != NULL) + ret = pcie_hp_create_port_name_num(dip, cn_info); + else + ret = pcie_hp_setup_port_name_num(pdip, cn_name, cn_info); + + if (ret != DDI_SUCCESS) { + kmem_free(cn_info, sizeof (ddi_hp_cn_info_t)); + return (ret); + } + + cn_info->cn_child = dip; + cn_info->cn_type = DDI_HP_CN_TYPE_VIRTUAL_PORT; + cn_info->cn_type_str = DDI_HP_CN_TYPE_STR_PORT; + + ret = ndi_hp_register(pdip, cn_info); + + kmem_free(cn_info->cn_name, strlen(cn_info->cn_name) + 1); + kmem_free(cn_info, sizeof (ddi_hp_cn_info_t)); + + return (ndi2ddi(ret)); +} + +/* Check if there is a piece of hardware exist corresponding to the cn_name */ +static int +pcie_hp_check_hardware_existence(dev_info_t *dip, int dev_num, int func_num) +{ + + /* + * VHPTODO: + * According to device and function number, check if there is a hardware + * device exists. Currently, this function can not be reached before + * we enable state transition to or from "Port-Empty" or "Port-Present" + * states. When the pci device type project is integrated, we are going + * to call the pci config space access interfaces introduced by it. + */ + _NOTE(ARGUNUSED(dip, dev_num, func_num)); + + return (DDI_SUCCESS); +} + +/* + * Dispatch hotplug commands to different hotplug controller drivers, including + * physical and virtual hotplug operations. + */ +/* ARGSUSED */ +int +pcie_hp_common_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op, + void *arg, void *result) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + int ret = DDI_SUCCESS; + + PCIE_DBG("pcie_hp_common_ops: dip=%p cn_name=%s op=%x arg=%p\n", + dip, cn_name, op, arg); + + switch (op) { + case DDI_HPOP_CN_CREATE_PORT: + { + /* create an empty port */ + return (pcie_hp_register_port(NULL, dip, cn_name)); + } + case DDI_HPOP_CN_CHANGE_STATE: + { + ddi_hp_cn_state_t curr_state; + ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; + pcie_hp_port_state_t state_arg; + + if (target_state < DDI_HP_CN_STATE_PORT_EMPTY) { + /* this is for physical slot state change */ + break; + } + PCIE_DBG("pcie_hp_common_ops: change port state" + " dip=%p cn_name=%s" + " op=%x arg=%p\n", (void *)dip, cn_name, op, arg); + + state_arg.rv = DDI_FAILURE; + state_arg.cn_name = cn_name; + ndi_hp_walk_cn(dip, pcie_hp_get_port_state, &state_arg); + if (state_arg.rv != DDI_SUCCESS) { + /* can not find the port */ + return (DDI_EINVAL); + } + curr_state = state_arg.cn_state; + /* + * Check if this is for changing port's state: change to/from + * PORT_EMPTY/PRESENT states. + */ + if (curr_state < target_state) { + /* Upgrade state */ + switch (curr_state) { + case DDI_HP_CN_STATE_PORT_EMPTY: + if (target_state == + DDI_HP_CN_STATE_PORT_PRESENT) { + int dev_num, func_num; + + ret = pcie_hp_get_df_from_port_name( + cn_name, &dev_num, &func_num); + if (ret != DDI_SUCCESS) + goto port_state_done; + + ret = pcie_hp_check_hardware_existence( + dip, dev_num, func_num); + } else if (target_state == + DDI_HP_CN_STATE_OFFLINE) { + ret = pcie_read_only_probe(dip, + cn_name, (dev_info_t **)result); + } else + ret = DDI_EINVAL; + + goto port_state_done; + case DDI_HP_CN_STATE_PORT_PRESENT: + if (target_state == + DDI_HP_CN_STATE_OFFLINE) + ret = pcie_read_only_probe(dip, + cn_name, (dev_info_t **)result); + else + ret = DDI_EINVAL; + + goto port_state_done; + default: + ASSERT("unexpected state"); + } + } else { + /* Downgrade state */ + switch (curr_state) { + case DDI_HP_CN_STATE_PORT_PRESENT: + { + int dev_num, func_num; + + ret = pcie_hp_get_df_from_port_name(cn_name, + &dev_num, &func_num); + if (ret != DDI_SUCCESS) + goto port_state_done; + + ret = pcie_hp_check_hardware_existence(dip, + dev_num, func_num); + + goto port_state_done; + } + case DDI_HP_CN_STATE_OFFLINE: + ret = pcie_read_only_unprobe(dip, cn_name); + + goto port_state_done; + default: + ASSERT("unexpected state"); + } + } +port_state_done: + *(ddi_hp_cn_state_t *)result = curr_state; + return (ret); + } + default: + break; + } + + if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) { + /* PCIe hotplug */ + ret = pciehpc_hp_ops(dip, cn_name, op, arg, result); + } else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) { + /* PCI SHPC hotplug */ + ret = pcishpc_hp_ops(dip, cn_name, op, arg, result); + } else { + cmn_err(CE_WARN, "pcie_hp_common_ops: op is not supported." + " dip=%p cn_name=%s" + " op=%x arg=%p\n", (void *)dip, cn_name, op, arg); + ret = DDI_ENOTSUP; + } + +#if defined(__i386) || defined(__amd64) + /* + * like in attach, since hotplugging can change error registers, + * we need to ensure that the proper bits are set on this port + * after a configure operation + */ + if ((ret == DDI_SUCCESS) && (op == DDI_HPOP_CN_CHANGE_STATE) && + (*(ddi_hp_cn_state_t *)arg == DDI_HP_CN_STATE_ENABLED)) + pcieb_intel_error_workaround(dip); +#endif + + return (ret); +} + +/* + * pcie_hp_match_dev_func: + * Match dip's PCI device number and function number with input ones. + */ +static int +pcie_hp_match_dev_func(dev_info_t *dip, void *hdl) +{ + struct pcie_hp_find_ctrl *ctrl = (struct pcie_hp_find_ctrl *)hdl; + pci_regspec_t *pci_rp; + int length; + int pci_dev, pci_func; + + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "reg", (int **)&pci_rp, (uint_t *)&length) != DDI_PROP_SUCCESS) { + ctrl->dip = NULL; + return (DDI_WALK_TERMINATE); + } + + /* get the PCI device address info */ + pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi); + pci_func = PCI_REG_FUNC_G(pci_rp->pci_phys_hi); + + /* + * free the memory allocated by ddi_prop_lookup_int_array + */ + ddi_prop_free(pci_rp); + + if ((pci_dev == ctrl->device) && (pci_func == ctrl->function)) { + /* found the match for the specified device address */ + ctrl->dip = dip; + return (DDI_WALK_TERMINATE); + } + + /* + * continue the walk to the next sibling to look for a match. + */ + return (DDI_WALK_PRUNECHILD); +} + +/* + * pcie_hp_match_dev: + * Match the dip's pci device number with the input dev_num + */ +static boolean_t +pcie_hp_match_dev(dev_info_t *dip, int dev_num) +{ + pci_regspec_t *pci_rp; + int length; + int pci_dev; + + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "reg", (int **)&pci_rp, (uint_t *)&length) != DDI_PROP_SUCCESS) { + return (B_FALSE); + } + + /* get the PCI device address info */ + pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi); + + /* + * free the memory allocated by ddi_prop_lookup_int_array + */ + ddi_prop_free(pci_rp); + + if (pci_dev == dev_num) { + /* found the match for the specified device address */ + return (B_TRUE); + } + + return (B_FALSE); +} + +/* + * Callback function to match with device number in order to list + * occupants under a specific slot + */ +static int +pcie_hp_list_occupants(dev_info_t *dip, void *arg) +{ + pcie_hp_cn_cfg_t *cn_cfg_p = (pcie_hp_cn_cfg_t *)arg; + pcie_hp_occupant_info_t *occupant = + (pcie_hp_occupant_info_t *)cn_cfg_p->cn_private; + pcie_hp_slot_t *slot_p = + (pcie_hp_slot_t *)cn_cfg_p->slotp; + int pci_dev; + pci_regspec_t *pci_rp; + int length; + major_t major; + + /* + * Get the PCI device number information from the devinfo + * node. Since the node may not have the address field + * setup (this is done in the DDI_INITCHILD of the parent) + * we look up the 'reg' property to decode that information. + */ + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, + DDI_PROP_DONTPASS, "reg", (int **)&pci_rp, + (uint_t *)&length) != DDI_PROP_SUCCESS) { + cn_cfg_p->rv = DDI_FAILURE; + cn_cfg_p->dip = dip; + return (DDI_WALK_TERMINATE); + } + + /* get the pci device id information */ + pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi); + + /* + * free the memory allocated by ddi_prop_lookup_int_array + */ + ddi_prop_free(pci_rp); + + /* + * Match the node for the device number of the slot. + */ + if (pci_dev == slot_p->hs_device_num) { + + major = ddi_driver_major(dip); + + /* + * If the node is not yet attached, then don't list it + * as an occupant. This is valid, since nothing can be + * consuming it until it is attached, and cfgadm will + * ask for the property explicitly which will cause it + * to be re-freshed right before checking with rcm. + */ + if ((major == DDI_MAJOR_T_NONE) || !i_ddi_devi_attached(dip)) + return (DDI_WALK_PRUNECHILD); + + /* + * If we have used all our occupants then print mesage + * and terminate walk. + */ + if (occupant->i >= PCIE_HP_MAX_OCCUPANTS) { + cmn_err(CE_WARN, + "pcie (%s%d): unable to list all occupants", + ddi_driver_name(ddi_get_parent(dip)), + ddi_get_instance(ddi_get_parent(dip))); + return (DDI_WALK_TERMINATE); + } + + /* + * No need to hold the dip as ddi_walk_devs + * has already arranged that for us. + */ + occupant->id[occupant->i] = + kmem_alloc(sizeof (char[MAXPATHLEN]), KM_SLEEP); + (void) ddi_pathname(dip, (char *)occupant->id[occupant->i]); + occupant->i++; + } + + /* + * continue the walk to the next sibling to look for a match + * or to find other nodes if this card is a multi-function card. + */ + return (DDI_WALK_PRUNECHILD); +} + +/* + * Generate the System Event for ESC_DR_REQ. + * One of the consumers is pcidr, it calls to libcfgadm to perform a + * configure or unconfigure operation to the AP. + */ +void +pcie_hp_gen_sysevent_req(char *slot_name, int hint, + dev_info_t *self, int kmflag) +{ + sysevent_id_t eid; + nvlist_t *ev_attr_list = NULL; + char cn_path[MAXPATHLEN]; + char *ap_id; + int err, ap_id_len; + + /* + * Minor device name (AP) will be bus path + * concatenated with slot name + */ + (void) strcpy(cn_path, "/devices"); + (void) ddi_pathname(self, cn_path + strlen("/devices")); + + ap_id_len = strlen(cn_path) + strlen(":") + + strlen(slot_name) + 1; + ap_id = kmem_zalloc(ap_id_len, kmflag); + if (ap_id == NULL) { + cmn_err(CE_WARN, + "%s%d: Failed to allocate memory for AP ID: %s:%s", + ddi_driver_name(self), ddi_get_instance(self), + cn_path, slot_name); + + return; + } + + (void) strcpy(ap_id, cn_path); + (void) strcat(ap_id, ":"); + (void) strcat(ap_id, slot_name); + + err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag); + if (err != 0) { + cmn_err(CE_WARN, + "%s%d: Failed to allocate memory " + "for event attributes%s", ddi_driver_name(self), + ddi_get_instance(self), ESC_DR_REQ); + + kmem_free(ap_id, ap_id_len); + return; + } + + switch (hint) { + + case SE_INVESTIGATE_RES: /* fall through */ + case SE_INCOMING_RES: /* fall through */ + case SE_OUTGOING_RES: /* fall through */ + + err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE, + SE_REQ2STR(hint)); + + if (err != 0) { + cmn_err(CE_WARN, + "%s%d: Failed to add attr [%s] " + "for %s event", ddi_driver_name(self), + ddi_get_instance(self), + DR_REQ_TYPE, ESC_DR_REQ); + + goto done; + } + break; + + default: + cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent", + ddi_driver_name(self), ddi_get_instance(self)); + + goto done; + } + + /* + * Add attachment point as attribute (common attribute) + */ + + err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap_id); + + if (err != 0) { + cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event", + ddi_driver_name(self), ddi_get_instance(self), + DR_AP_ID, EC_DR); + + goto done; + } + + + /* + * Log this event with sysevent framework. + */ + + err = ddi_log_sysevent(self, DDI_VENDOR_SUNW, EC_DR, + ESC_DR_REQ, ev_attr_list, &eid, + ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP)); + if (err != 0) { + cmn_err(CE_WARN, "%s%d: Failed to log %s event", + ddi_driver_name(self), ddi_get_instance(self), EC_DR); + } + +done: + nvlist_free(ev_attr_list); + kmem_free(ap_id, ap_id_len); +} diff --git a/usr/src/uts/common/io/pciex/hotplug/pciehpc.c b/usr/src/uts/common/io/pciex/hotplug/pciehpc.c new file mode 100644 index 0000000000..900e36b383 --- /dev/null +++ b/usr/src/uts/common/io/pciex/hotplug/pciehpc.c @@ -0,0 +1,2285 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * This file contains Standard PCI Express HotPlug functionality that is + * compatible with the PCI Express ver 1.1 specification. + * + * NOTE: This file is compiled and delivered through misc/pcie module. + */ + +#include <sys/types.h> +#include <sys/note.h> +#include <sys/conf.h> +#include <sys/kmem.h> +#include <sys/debug.h> +#include <sys/vtrace.h> +#include <sys/autoconf.h> +#include <sys/varargs.h> +#include <sys/ddi_impldefs.h> +#include <sys/time.h> +#include <sys/callb.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/sysevent/dr.h> +#include <sys/pci_impl.h> +#include <sys/hotplug/pci/pcie_hp.h> +#include <sys/hotplug/pci/pciehpc.h> + +typedef struct pciehpc_prop { + char *prop_name; + char *prop_value; +} pciehpc_prop_t; + +static pciehpc_prop_t pciehpc_props[] = { + { PCIEHPC_PROP_LED_FAULT, PCIEHPC_PROP_VALUE_LED }, + { PCIEHPC_PROP_LED_POWER, PCIEHPC_PROP_VALUE_LED }, + { PCIEHPC_PROP_LED_ATTN, PCIEHPC_PROP_VALUE_LED }, + { PCIEHPC_PROP_LED_ACTIVE, PCIEHPC_PROP_VALUE_LED }, + { PCIEHPC_PROP_CARD_TYPE, PCIEHPC_PROP_VALUE_TYPE }, + { PCIEHPC_PROP_BOARD_TYPE, PCIEHPC_PROP_VALUE_TYPE }, + { PCIEHPC_PROP_SLOT_CONDITION, PCIEHPC_PROP_VALUE_TYPE } +}; + +/* Local functions prototype */ +static int pciehpc_hpc_init(pcie_hp_ctrl_t *ctrl_p); +static int pciehpc_hpc_uninit(pcie_hp_ctrl_t *ctrl_p); +static int pciehpc_slotinfo_init(pcie_hp_ctrl_t *ctrl_p); +static int pciehpc_slotinfo_uninit(pcie_hp_ctrl_t *ctrl_p); +static int pciehpc_enable_intr(pcie_hp_ctrl_t *ctrl_p); +static int pciehpc_disable_intr(pcie_hp_ctrl_t *ctrl_p); +static pcie_hp_ctrl_t *pciehpc_create_controller(dev_info_t *dip); +static void pciehpc_destroy_controller(dev_info_t *dip); +static int pciehpc_register_slot(pcie_hp_ctrl_t *ctrl_p); +static int pciehpc_unregister_slot(pcie_hp_ctrl_t *ctrl_p); +static int pciehpc_slot_get_property(pcie_hp_slot_t *slot_p, + ddi_hp_property_t *arg, ddi_hp_property_t *rval); +static int pciehpc_slot_set_property(pcie_hp_slot_t *slot_p, + ddi_hp_property_t *arg, ddi_hp_property_t *rval); +static void pciehpc_issue_hpc_command(pcie_hp_ctrl_t *ctrl_p, uint16_t control); +static void pciehpc_attn_btn_handler(pcie_hp_ctrl_t *ctrl_p); +static pcie_hp_led_state_t pciehpc_led_state_to_hpc(uint16_t state); +static pcie_hp_led_state_t pciehpc_get_led_state(pcie_hp_ctrl_t *ctrl_p, + pcie_hp_led_t led); +static void pciehpc_set_led_state(pcie_hp_ctrl_t *ctrl_p, pcie_hp_led_t led, + pcie_hp_led_state_t state); + +static int pciehpc_upgrade_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state); +static int pciehpc_downgrade_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state); +static int pciehpc_change_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state); +static int + pciehpc_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result); +static int + pciehpc_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result); +static int pciehpc_slot_probe(pcie_hp_slot_t *slot_p); +static int pciehpc_slot_unprobe(pcie_hp_slot_t *slot_p); + +#ifdef DEBUG +static void pciehpc_dump_hpregs(pcie_hp_ctrl_t *ctrl_p); +#endif /* DEBUG */ + +/* + * Global functions (called by other drivers/modules) + */ + +/* + * Initialize Hot Plug Controller if present. The arguments are: + * dip - Devinfo node pointer to the hot plug bus node + * regops - register ops to access HPC registers for non-standard + * HPC hw implementations (e.g: HPC in host PCI-E brdiges) + * This is NULL for standard HPC in PCIe bridges. + * Returns: + * DDI_SUCCESS for successful HPC initialization + * DDI_FAILURE for errors or if HPC hw not found + */ +int +pciehpc_init(dev_info_t *dip, caddr_t arg) +{ + pcie_hp_regops_t *regops = (pcie_hp_regops_t *)(void *)arg; + pcie_hp_ctrl_t *ctrl_p; + + PCIE_DBG("pciehpc_init() called (dip=%p)\n", (void *)dip); + + /* Make sure that it is not already initialized */ + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) != NULL) { + PCIE_DBG("%s%d: pciehpc instance already initialized!\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + return (DDI_SUCCESS); + } + + /* Allocate a new hotplug controller and slot structures */ + ctrl_p = pciehpc_create_controller(dip); + + /* setup access handle for HPC regs */ + if (regops != NULL) { + /* HPC access is non-standard; use the supplied reg ops */ + ctrl_p->hc_regops = *regops; + } + + /* + * Setup resource maps for this bus node. + */ + (void) pci_resource_setup(dip); + + PCIE_DISABLE_ERRORS(dip); + + /* + * Set the platform specific hot plug mode. + */ + ctrl_p->hc_ops.init_hpc_hw = pciehpc_hpc_init; + ctrl_p->hc_ops.uninit_hpc_hw = pciehpc_hpc_uninit; + ctrl_p->hc_ops.init_hpc_slotinfo = pciehpc_slotinfo_init; + ctrl_p->hc_ops.uninit_hpc_slotinfo = pciehpc_slotinfo_uninit; + ctrl_p->hc_ops.poweron_hpc_slot = pciehpc_slot_poweron; + ctrl_p->hc_ops.poweroff_hpc_slot = pciehpc_slot_poweroff; + + ctrl_p->hc_ops.enable_hpc_intr = pciehpc_enable_intr; + ctrl_p->hc_ops.disable_hpc_intr = pciehpc_disable_intr; + +#if defined(__i386) || defined(__amd64) + pciehpc_update_ops(ctrl_p); +#endif + + /* initialize hot plug controller hw */ + if ((ctrl_p->hc_ops.init_hpc_hw)(ctrl_p) != DDI_SUCCESS) + goto cleanup1; + + /* initialize slot information soft state structure */ + if ((ctrl_p->hc_ops.init_hpc_slotinfo)(ctrl_p) != DDI_SUCCESS) + goto cleanup2; + + /* register the hot plug slot with DDI HP framework */ + if (pciehpc_register_slot(ctrl_p) != DDI_SUCCESS) + goto cleanup3; + + /* create minor node for this slot */ + if (pcie_create_minor_node(ctrl_p, 0) != DDI_SUCCESS) + goto cleanup4; + + /* HPC initialization is complete now */ + ctrl_p->hc_flags = PCIE_HP_INITIALIZED_FLAG; + +#ifdef DEBUG + /* For debug, dump the HPC registers */ + pciehpc_dump_hpregs(ctrl_p); +#endif /* DEBUG */ + + /* enable hot plug interrupts/event */ + (void) (ctrl_p->hc_ops.enable_hpc_intr)(ctrl_p); + + return (DDI_SUCCESS); +cleanup4: + (void) pciehpc_unregister_slot(ctrl_p); +cleanup3: + (void) (ctrl_p->hc_ops.uninit_hpc_slotinfo)(ctrl_p); + +cleanup2: + (void) (ctrl_p->hc_ops.uninit_hpc_hw)(ctrl_p); + +cleanup1: + PCIE_ENABLE_ERRORS(dip); + (void) pci_resource_destroy(dip); + + pciehpc_destroy_controller(dip); + return (DDI_FAILURE); +} + +/* + * Uninitialize HPC soft state structure and free up any resources + * used for the HPC instance. + */ +int +pciehpc_uninit(dev_info_t *dip) +{ + pcie_hp_ctrl_t *ctrl_p; + + PCIE_DBG("pciehpc_uninit() called (dip=%p)\n", (void *)dip); + + /* get the soft state structure for this dip */ + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) { + return (DDI_FAILURE); + } + + pcie_remove_minor_node(ctrl_p, 0); + + /* disable interrupts */ + (void) (ctrl_p->hc_ops.disable_hpc_intr)(ctrl_p); + + /* unregister the slot */ + (void) pciehpc_unregister_slot(ctrl_p); + + /* uninit any slot info data structures */ + (void) (ctrl_p->hc_ops.uninit_hpc_slotinfo)(ctrl_p); + + /* uninitialize hpc, remove interrupt handler, etc. */ + (void) (ctrl_p->hc_ops.uninit_hpc_hw)(ctrl_p); + + PCIE_ENABLE_ERRORS(dip); + + /* + * Destroy resource maps for this bus node. + */ + (void) pci_resource_destroy(dip); + + /* destroy the soft state structure */ + pciehpc_destroy_controller(dip); + + return (DDI_SUCCESS); +} + +/* + * pciehpc_intr() + * + * Interrupt handler for PCI-E Hot plug controller interrupts. + * + * Note: This is only for native mode hot plug. This is called + * by the nexus driver at interrupt context. Interrupt Service Routine + * registration is done by the nexus driver for both hot plug and + * non-hot plug interrupts. This function is called from the ISR + * of the nexus driver to handle hot-plug interrupts. + */ +int +pciehpc_intr(dev_info_t *dip) +{ + pcie_hp_ctrl_t *ctrl_p; + pcie_hp_slot_t *slot_p; + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + uint16_t status, control; + + /* get the soft state structure for this dip */ + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) + return (DDI_INTR_UNCLAIMED); + + mutex_enter(&ctrl_p->hc_mutex); + + /* make sure the controller soft state is initialized */ + if (!(ctrl_p->hc_flags & PCIE_HP_INITIALIZED_FLAG)) { + mutex_exit(&ctrl_p->hc_mutex); + return (DDI_INTR_UNCLAIMED); + } + + /* if it is not NATIVE hot plug mode then return */ + if (bus_p->bus_hp_curr_mode != PCIE_NATIVE_HP_MODE) { + mutex_exit(&ctrl_p->hc_mutex); + return (DDI_INTR_UNCLAIMED); + } + + slot_p = ctrl_p->hc_slots[0]; + + /* read the current slot status register */ + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + + /* check if there are any hot plug interrupts occurred */ + if (!(status & PCIE_SLOTSTS_STATUS_EVENTS)) { + /* no hot plug events occurred */ + mutex_exit(&ctrl_p->hc_mutex); + return (DDI_INTR_UNCLAIMED); + } + + /* clear the interrupt status bits */ + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS, status); + + /* check for CMD COMPLETE interrupt */ + if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) { + PCIE_DBG("pciehpc_intr(): CMD COMPLETED interrupt received\n"); + /* wake up any one waiting for Command Completion event */ + cv_signal(&ctrl_p->hc_cmd_comp_cv); + } + + /* check for ATTN button interrupt */ + if (status & PCIE_SLOTSTS_ATTN_BTN_PRESSED) { + PCIE_DBG("pciehpc_intr(): ATTN BUTTON interrupt received\n"); + + /* if ATTN button event is still pending then cancel it */ + if (slot_p->hs_attn_btn_pending == B_TRUE) + slot_p->hs_attn_btn_pending = B_FALSE; + else + slot_p->hs_attn_btn_pending = B_TRUE; + + /* wake up the ATTN event handler */ + cv_signal(&slot_p->hs_attn_btn_cv); + } + + /* check for power fault interrupt */ + if (status & PCIE_SLOTSTS_PWR_FAULT_DETECTED) { + + PCIE_DBG("pciehpc_intr(): POWER FAULT interrupt received" + " on slot %d\n", slot_p->hs_phy_slot_num); + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + if (control & PCIE_SLOTCTL_PWR_FAULT_EN) { + slot_p->hs_condition = AP_COND_FAILED; + + /* disable power fault detction interrupt */ + pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + + PCIE_SLOTCTL, control & ~PCIE_SLOTCTL_PWR_FAULT_EN); + + /* + * Send the event to DDI Hotplug framework, power off + * the slot + */ + (void) ndi_hp_state_change_req(dip, + slot_p->hs_info.cn_name, + DDI_HP_CN_STATE_EMPTY, DDI_HP_REQ_ASYNC); + + pciehpc_set_led_state(ctrl_p, PCIE_HP_ATTN_LED, + PCIE_HP_LED_ON); + } + } + + /* check for MRL SENSOR CHANGED interrupt */ + if (status & PCIE_SLOTSTS_MRL_SENSOR_CHANGED) { + /* For now (phase-I), no action is taken on this event */ + PCIE_DBG("pciehpc_intr(): MRL SENSOR CHANGED interrupt received" + " on slot %d\n", slot_p->hs_phy_slot_num); + } + + /* check for PRESENCE CHANGED interrupt */ + if (status & PCIE_SLOTSTS_PRESENCE_CHANGED) { + + PCIE_DBG("pciehpc_intr(): PRESENCE CHANGED interrupt received" + " on slot %d\n", slot_p->hs_phy_slot_num); + + if (status & PCIE_SLOTSTS_PRESENCE_DETECTED) { + /* + * card is inserted into the slot, ask DDI Hotplug + * framework to change state to Present. + */ + (void) ndi_hp_state_change_req(dip, + slot_p->hs_info.cn_name, + DDI_HP_CN_STATE_PRESENT, + DDI_HP_REQ_ASYNC); + } else { /* card is removed from the slot */ + cmn_err(CE_NOTE, "pciehpc (%s%d): card is removed" + " from the slot %s", + ddi_driver_name(dip), + ddi_get_instance(dip), + slot_p->hs_info.cn_name); + + if (slot_p->hs_info.cn_state == + DDI_HP_CN_STATE_ENABLED) { + /* Card is removed when slot is enabled */ + slot_p->hs_condition = AP_COND_FAILED; + } else { + slot_p->hs_condition = AP_COND_UNKNOWN; + } + /* make sure to disable power fault detction intr */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + if (control & PCIE_SLOTCTL_PWR_FAULT_EN) + pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + + PCIE_SLOTCTL, + control & ~PCIE_SLOTCTL_PWR_FAULT_EN); + + /* + * Ask DDI Hotplug framework to change state to Empty + */ + (void) ndi_hp_state_change_req(dip, + slot_p->hs_info.cn_name, + DDI_HP_CN_STATE_EMPTY, + DDI_HP_REQ_ASYNC); + } + } + + /* check for DLL state changed interrupt */ + if (ctrl_p->hc_dll_active_rep && + (status & PCIE_SLOTSTS_DLL_STATE_CHANGED)) { + PCIE_DBG("pciehpc_intr(): DLL STATE CHANGED interrupt received" + " on slot %d\n", slot_p->hs_phy_slot_num); + + cv_signal(&slot_p->hs_dll_active_cv); + } + + mutex_exit(&ctrl_p->hc_mutex); + + return (DDI_INTR_CLAIMED); +} + +/* + * Handle hotplug commands + * + * Note: This function is called by DDI HP framework at kernel context only + */ +/* ARGSUSED */ +int +pciehpc_hp_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op, + void *arg, void *result) +{ + pcie_hp_ctrl_t *ctrl_p; + pcie_hp_slot_t *slot_p; + int ret = DDI_SUCCESS; + + PCIE_DBG("pciehpc_hp_ops: dip=%p cn_name=%s op=%x arg=%p\n", + dip, cn_name, op, arg); + + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) + return (DDI_FAILURE); + + slot_p = ctrl_p->hc_slots[0]; + + if (strcmp(cn_name, slot_p->hs_info.cn_name) != 0) + return (DDI_EINVAL); + + switch (op) { + case DDI_HPOP_CN_GET_STATE: + { + mutex_enter(&slot_p->hs_ctrl->hc_mutex); + + /* get the current slot state */ + pciehpc_get_slot_state(slot_p); + + *((ddi_hp_cn_state_t *)result) = slot_p->hs_info.cn_state; + + mutex_exit(&slot_p->hs_ctrl->hc_mutex); + break; + } + case DDI_HPOP_CN_CHANGE_STATE: + { + ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; + + mutex_enter(&slot_p->hs_ctrl->hc_mutex); + + ret = pciehpc_change_slot_state(slot_p, target_state); + *(ddi_hp_cn_state_t *)result = slot_p->hs_info.cn_state; + + mutex_exit(&slot_p->hs_ctrl->hc_mutex); + break; + } + case DDI_HPOP_CN_PROBE: + + ret = pciehpc_slot_probe(slot_p); + + break; + case DDI_HPOP_CN_UNPROBE: + ret = pciehpc_slot_unprobe(slot_p); + + break; + case DDI_HPOP_CN_GET_PROPERTY: + ret = pciehpc_slot_get_property(slot_p, + (ddi_hp_property_t *)arg, (ddi_hp_property_t *)result); + break; + case DDI_HPOP_CN_SET_PROPERTY: + ret = pciehpc_slot_set_property(slot_p, + (ddi_hp_property_t *)arg, (ddi_hp_property_t *)result); + break; + default: + ret = DDI_ENOTSUP; + break; + } + + return (ret); +} + +/* + * Get the current state of the slot from the hw. + * + * The slot state should have been initialized before this function gets called. + */ +void +pciehpc_get_slot_state(pcie_hp_slot_t *slot_p) +{ + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t control, status; + ddi_hp_cn_state_t curr_state = slot_p->hs_info.cn_state; + + /* read the Slot Control Register */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + slot_p->hs_fault_led_state = PCIE_HP_LED_OFF; /* no fault led */ + slot_p->hs_active_led_state = PCIE_HP_LED_OFF; /* no active led */ + + /* read the current Slot Status Register */ + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + + /* get POWER led state */ + slot_p->hs_power_led_state = + pciehpc_led_state_to_hpc(pcie_slotctl_pwr_indicator_get(control)); + + /* get ATTN led state */ + slot_p->hs_attn_led_state = + pciehpc_led_state_to_hpc(pcie_slotctl_attn_indicator_get(control)); + + if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) { + /* no device present; slot is empty */ + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_EMPTY; + + return; + } + + /* device is present */ + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_PRESENT; + + if (!(control & PCIE_SLOTCTL_PWR_CONTROL)) { + /* + * Device is powered on. Set to "ENABLED" state (skip + * POWERED state) because there is not a explicit "enable" + * action exists for PCIe. + * If it is already in "POWERED" state, then keep it until + * user explicitly change it to other states. + */ + if (curr_state == DDI_HP_CN_STATE_POWERED) { + slot_p->hs_info.cn_state = curr_state; + } else { + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_ENABLED; + } + } +} + +/* + * setup slot name/slot-number info. + */ +void +pciehpc_set_slot_name(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uchar_t *slotname_data; + int *slotnum; + uint_t count; + int len; + int invalid_slotnum = 0; + uint32_t slot_capabilities; + + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ctrl_p->hc_dip, + DDI_PROP_DONTPASS, "physical-slot#", &slotnum, &count) == + DDI_PROP_SUCCESS) { + slot_p->hs_phy_slot_num = slotnum[0]; + ddi_prop_free(slotnum); + } else { + slot_capabilities = pciehpc_reg_get32(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCAP); + slot_p->hs_phy_slot_num = + PCIE_SLOTCAP_PHY_SLOT_NUM(slot_capabilities); + } + + /* platform may not have initialized it */ + if (!slot_p->hs_phy_slot_num) { + PCIE_DBG("%s#%d: Invalid slot number!\n", + ddi_driver_name(ctrl_p->hc_dip), + ddi_get_instance(ctrl_p->hc_dip)); + slot_p->hs_phy_slot_num = pciehpc_reg_get8(ctrl_p, + PCI_BCNF_SECBUS); + invalid_slotnum = 1; + } + slot_p->hs_info.cn_num = slot_p->hs_phy_slot_num; + slot_p->hs_info.cn_num_dpd_on = DDI_HP_CN_NUM_NONE; + + /* + * construct the slot_name: + * if "slot-names" property exists then use that name + * else if valid slot number exists then it is "pcie<slot-num>". + * else it will be "pcie<sec-bus-number>dev0" + */ + if (ddi_getlongprop(DDI_DEV_T_ANY, ctrl_p->hc_dip, DDI_PROP_DONTPASS, + "slot-names", (caddr_t)&slotname_data, &len) == DDI_PROP_SUCCESS) { + char tmp_name[256]; + + /* + * Note: for PCI-E slots, the device number is always 0 so the + * first (and only) string is the slot name for this slot. + */ + (void) snprintf(tmp_name, sizeof (tmp_name), + (char *)slotname_data + 4); + slot_p->hs_info.cn_name = ddi_strdup(tmp_name, KM_SLEEP); + kmem_free(slotname_data, len); + } else { + if (invalid_slotnum) { + /* use device number ie. 0 */ + slot_p->hs_info.cn_name = ddi_strdup("pcie0", + KM_SLEEP); + } else { + char tmp_name[256]; + + (void) snprintf(tmp_name, sizeof (tmp_name), "pcie%d", + slot_p->hs_phy_slot_num); + slot_p->hs_info.cn_name = ddi_strdup(tmp_name, + KM_SLEEP); + } + } +} + +/* + * Read/Write access to HPC registers. If platform nexus has non-standard + * HPC access mechanism then regops functions are used to do reads/writes. + */ +uint8_t +pciehpc_reg_get8(pcie_hp_ctrl_t *ctrl_p, uint_t off) +{ + if (ctrl_p->hc_regops.get != NULL) { + return ((uint8_t)ctrl_p->hc_regops.get( + ctrl_p->hc_regops.cookie, (off_t)off)); + } else { + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + return (pci_config_get8(bus_p->bus_cfg_hdl, off)); + } +} + +uint16_t +pciehpc_reg_get16(pcie_hp_ctrl_t *ctrl_p, uint_t off) +{ + if (ctrl_p->hc_regops.get != NULL) { + return ((uint16_t)ctrl_p->hc_regops.get( + ctrl_p->hc_regops.cookie, (off_t)off)); + } else { + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + return (pci_config_get16(bus_p->bus_cfg_hdl, off)); + } +} + +uint32_t +pciehpc_reg_get32(pcie_hp_ctrl_t *ctrl_p, uint_t off) +{ + if (ctrl_p->hc_regops.get != NULL) { + return ((uint32_t)ctrl_p->hc_regops.get( + ctrl_p->hc_regops.cookie, (off_t)off)); + } else { + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + return (pci_config_get32(bus_p->bus_cfg_hdl, off)); + } +} + +void +pciehpc_reg_put8(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint8_t val) +{ + if (ctrl_p->hc_regops.put != NULL) { + ctrl_p->hc_regops.put(ctrl_p->hc_regops.cookie, + (off_t)off, (uint_t)val); + } else { + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + pci_config_put8(bus_p->bus_cfg_hdl, off, val); + } +} + +void +pciehpc_reg_put16(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint16_t val) +{ + if (ctrl_p->hc_regops.put != NULL) { + ctrl_p->hc_regops.put(ctrl_p->hc_regops.cookie, + (off_t)off, (uint_t)val); + } else { + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + pci_config_put16(bus_p->bus_cfg_hdl, off, val); + } +} + +void +pciehpc_reg_put32(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint32_t val) +{ + if (ctrl_p->hc_regops.put != NULL) { + ctrl_p->hc_regops.put(ctrl_p->hc_regops.cookie, + (off_t)off, (uint_t)val); + } else { + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + pci_config_put32(bus_p->bus_cfg_hdl, off, val); + } +} + +/* + * ************************************************************************ + * *** Local functions (called within this file) + * *** PCIe Native Hotplug mode specific functions + * ************************************************************************ + */ + +/* + * Initialize HPC hardware, install interrupt handler, etc. It doesn't + * enable hot plug interrupts. + * + * (Note: It is called only from pciehpc_init().) + */ +static int +pciehpc_hpc_init(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t reg; + + /* read the Slot Control Register */ + reg = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + /* disable all interrupts */ + reg &= ~(PCIE_SLOTCTL_INTR_MASK); + pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + + PCIE_SLOTCTL, reg); + + /* clear any interrupt status bits */ + reg = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS, reg); + + return (DDI_SUCCESS); +} + +/* + * Uninitialize HPC hardware, uninstall interrupt handler, etc. + * + * (Note: It is called only from pciehpc_uninit().) + */ +static int +pciehpc_hpc_uninit(pcie_hp_ctrl_t *ctrl_p) +{ + /* disable interrupts */ + (void) pciehpc_disable_intr(ctrl_p); + + return (DDI_SUCCESS); +} + +/* + * Setup slot information for use with DDI HP framework. + */ +static int +pciehpc_slotinfo_init(pcie_hp_ctrl_t *ctrl_p) +{ + uint32_t slot_capabilities, link_capabilities; + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + mutex_enter(&ctrl_p->hc_mutex); + /* + * setup DDI HP framework slot information structure + */ + slot_p->hs_device_num = 0; + + slot_p->hs_info.cn_type = DDI_HP_CN_TYPE_PCIE; + slot_p->hs_info.cn_type_str = (ctrl_p->hc_regops.get == NULL) ? + PCIE_NATIVE_HP_TYPE : PCIE_PROP_HP_TYPE; + slot_p->hs_info.cn_child = NULL; + + slot_p->hs_minor = + PCI_MINOR_NUM(ddi_get_instance(ctrl_p->hc_dip), + slot_p->hs_device_num); + slot_p->hs_condition = AP_COND_UNKNOWN; + + /* read Slot Capabilities Register */ + slot_capabilities = pciehpc_reg_get32(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCAP); + + /* set slot-name/slot-number info */ + pciehpc_set_slot_name(ctrl_p); + + /* check if Attn Button present */ + ctrl_p->hc_has_attn = (slot_capabilities & PCIE_SLOTCAP_ATTN_BUTTON) ? + B_TRUE : B_FALSE; + + /* check if Manual Retention Latch sensor present */ + ctrl_p->hc_has_mrl = (slot_capabilities & PCIE_SLOTCAP_MRL_SENSOR) ? + B_TRUE : B_FALSE; + + /* + * PCI-E version 1.1 defines EMI Lock Present bit + * in Slot Capabilities register. Check for it. + */ + ctrl_p->hc_has_emi_lock = (slot_capabilities & + PCIE_SLOTCAP_EMI_LOCK_PRESENT) ? B_TRUE : B_FALSE; + + link_capabilities = pciehpc_reg_get32(ctrl_p, + bus_p->bus_pcie_off + PCIE_LINKCAP); + ctrl_p->hc_dll_active_rep = (link_capabilities & + PCIE_LINKCAP_DLL_ACTIVE_REP_CAPABLE) ? B_TRUE : B_FALSE; + if (ctrl_p->hc_dll_active_rep) + cv_init(&slot_p->hs_dll_active_cv, NULL, CV_DRIVER, NULL); + + /* setup thread for handling ATTN button events */ + if (ctrl_p->hc_has_attn) { + PCIE_DBG("pciehpc_slotinfo_init: setting up ATTN button event " + "handler thread for slot %d\n", slot_p->hs_phy_slot_num); + + cv_init(&slot_p->hs_attn_btn_cv, NULL, CV_DRIVER, NULL); + slot_p->hs_attn_btn_pending = B_FALSE; + slot_p->hs_attn_btn_threadp = thread_create(NULL, 0, + pciehpc_attn_btn_handler, + (void *)ctrl_p, 0, &p0, TS_RUN, minclsyspri); + slot_p->hs_attn_btn_thread_exit = B_FALSE; + } + + /* get current slot state from the hw */ + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_EMPTY; + pciehpc_get_slot_state(slot_p); + if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_ENABLED) + slot_p->hs_condition = AP_COND_OK; + + mutex_exit(&ctrl_p->hc_mutex); + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static int +pciehpc_slotinfo_uninit(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + + if (slot_p->hs_attn_btn_threadp != NULL) { + mutex_enter(&ctrl_p->hc_mutex); + slot_p->hs_attn_btn_thread_exit = B_TRUE; + cv_signal(&slot_p->hs_attn_btn_cv); + PCIE_DBG("pciehpc_slotinfo_uninit: " + "waiting for ATTN thread exit\n"); + cv_wait(&slot_p->hs_attn_btn_cv, &ctrl_p->hc_mutex); + PCIE_DBG("pciehpc_slotinfo_uninit: ATTN thread exit\n"); + cv_destroy(&slot_p->hs_attn_btn_cv); + slot_p->hs_attn_btn_threadp = NULL; + mutex_exit(&ctrl_p->hc_mutex); + } + + if (ctrl_p->hc_dll_active_rep) + cv_destroy(&slot_p->hs_dll_active_cv); + if (slot_p->hs_info.cn_name) + kmem_free(slot_p->hs_info.cn_name, + strlen(slot_p->hs_info.cn_name) + 1); + + return (DDI_SUCCESS); +} + +/* + * Enable hot plug interrupts. + * Note: this is only for Native hot plug mode. + */ +static int +pciehpc_enable_intr(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t reg; + + /* clear any interrupt status bits */ + reg = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS, reg); + + /* read the Slot Control Register */ + reg = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + /* + * enable interrupts: power fault detection interrupt is enabled + * only when the slot is powered ON + */ + if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) + pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + + PCIE_SLOTCTL, reg | PCIE_SLOTCTL_INTR_MASK); + else + pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + + PCIE_SLOTCTL, reg | (PCIE_SLOTCTL_INTR_MASK & + ~PCIE_SLOTCTL_PWR_FAULT_EN)); + + return (DDI_SUCCESS); +} + +/* + * Disable hot plug interrupts. + * Note: this is only for Native hot plug mode. + */ +static int +pciehpc_disable_intr(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t reg; + + /* read the Slot Control Register */ + reg = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + /* disable all interrupts */ + reg &= ~(PCIE_SLOTCTL_INTR_MASK); + pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTCTL, reg); + + /* clear any interrupt status bits */ + reg = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS, reg); + + return (DDI_SUCCESS); +} + +/* + * Allocate a new hotplug controller and slot structures for HPC + * associated with this dip. + */ +static pcie_hp_ctrl_t * +pciehpc_create_controller(dev_info_t *dip) +{ + pcie_hp_ctrl_t *ctrl_p; + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + + ctrl_p = kmem_zalloc(sizeof (pcie_hp_ctrl_t), KM_SLEEP); + ctrl_p->hc_dip = dip; + + /* Allocate a new slot structure. */ + ctrl_p->hc_slots[0] = kmem_zalloc(sizeof (pcie_hp_slot_t), KM_SLEEP); + ctrl_p->hc_slots[0]->hs_num = 0; + ctrl_p->hc_slots[0]->hs_ctrl = ctrl_p; + + /* Initialize the interrupt mutex */ + mutex_init(&ctrl_p->hc_mutex, NULL, MUTEX_DRIVER, + (void *)PCIE_INTR_PRI); + + /* Initialize synchronization conditional variable */ + cv_init(&ctrl_p->hc_cmd_comp_cv, NULL, CV_DRIVER, NULL); + ctrl_p->hc_cmd_pending = B_FALSE; + + bus_p->bus_hp_curr_mode = PCIE_NATIVE_HP_MODE; + PCIE_SET_HP_CTRL(dip, ctrl_p); + + return (ctrl_p); +} + +/* + * Remove the HPC controller and slot structures + */ +static void +pciehpc_destroy_controller(dev_info_t *dip) +{ + pcie_hp_ctrl_t *ctrl_p; + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + + /* get the soft state structure for this dip */ + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) + return; + + PCIE_SET_HP_CTRL(dip, NULL); + bus_p->bus_hp_curr_mode = PCIE_NONE_HP_MODE; + + mutex_destroy(&ctrl_p->hc_mutex); + cv_destroy(&ctrl_p->hc_cmd_comp_cv); + kmem_free(ctrl_p->hc_slots[0], sizeof (pcie_hp_slot_t)); + kmem_free(ctrl_p, sizeof (pcie_hp_ctrl_t)); +} + +/* + * Register the PCI-E hot plug slot with DDI HP framework. + */ +static int +pciehpc_register_slot(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + dev_info_t *dip = ctrl_p->hc_dip; + + /* register the slot with DDI HP framework */ + if (ndi_hp_register(dip, &slot_p->hs_info) != NDI_SUCCESS) { + PCIE_DBG("pciehpc_register_slot() failed to register slot %d\n", + slot_p->hs_phy_slot_num); + return (DDI_FAILURE); + } + + pcie_hp_create_occupant_props(dip, makedevice(ddi_driver_major(dip), + slot_p->hs_minor), slot_p->hs_device_num); + + PCIE_DBG("pciehpc_register_slot(): registered slot %d\n", + slot_p->hs_phy_slot_num); + + return (DDI_SUCCESS); +} + +/* + * Unregister the PCI-E hot plug slot from DDI HP framework. + */ +static int +pciehpc_unregister_slot(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + dev_info_t *dip = ctrl_p->hc_dip; + + pcie_hp_delete_occupant_props(dip, makedevice(ddi_driver_major(dip), + slot_p->hs_minor)); + + /* unregister the slot with DDI HP framework */ + if (ndi_hp_unregister(dip, slot_p->hs_info.cn_name) != NDI_SUCCESS) { + PCIE_DBG("pciehpc_unregister_slot() " + "failed to unregister slot %d\n", slot_p->hs_phy_slot_num); + return (DDI_FAILURE); + } + + PCIE_DBG("pciehpc_unregister_slot(): unregistered slot %d\n", + slot_p->hs_phy_slot_num); + + return (DDI_SUCCESS); +} + +/* + * pciehpc_slot_poweron() + * + * Poweron/Enable the slot. + * + * Note: This function is called by DDI HP framework at kernel context only + */ +/*ARGSUSED*/ +static int +pciehpc_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result) +{ + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t status, control; + + ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex)); + + /* get the current state of the slot */ + pciehpc_get_slot_state(slot_p); + + /* check if the slot is already in the 'enabled' state */ + if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) { + /* slot is already in the 'enabled' state */ + PCIE_DBG("pciehpc_slot_poweron() slot %d already enabled\n", + slot_p->hs_phy_slot_num); + + *result = slot_p->hs_info.cn_state; + return (DDI_SUCCESS); + } + + /* read the Slot Status Register */ + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + + /* make sure the MRL switch is closed if present */ + if ((ctrl_p->hc_has_mrl) && (status & PCIE_SLOTSTS_MRL_SENSOR_OPEN)) { + /* MRL switch is open */ + cmn_err(CE_WARN, "MRL switch is open on slot %d\n", + slot_p->hs_phy_slot_num); + goto cleanup; + } + + /* make sure the slot has a device present */ + if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) { + /* slot is empty */ + PCIE_DBG("slot %d is empty\n", slot_p->hs_phy_slot_num); + goto cleanup; + } + + /* get the current state of Slot Control Register */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + /* + * Enable power to the slot involves: + * 1. Set power LED to blink and ATTN led to OFF. + * 2. Set power control ON in Slot Control Reigster and + * wait for Command Completed Interrupt or 1 sec timeout. + * 3. If Data Link Layer State Changed events are supported + * then wait for the event to indicate Data Layer Link + * is active. The time out value for this event is 1 second. + * This is specified in PCI-E version 1.1. + * 4. Set power LED to be ON. + */ + + /* 1. set power LED to blink & ATTN led to OFF */ + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, PCIE_HP_LED_BLINK); + pciehpc_set_led_state(ctrl_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); + + /* 2. set power control to ON */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + control &= ~PCIE_SLOTCTL_PWR_CONTROL; + pciehpc_issue_hpc_command(ctrl_p, control); + + /* 3. wait for DLL State Change event, if it's supported */ + if (ctrl_p->hc_dll_active_rep) { + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_LINKSTS); + + if (!(status & PCIE_LINKSTS_DLL_LINK_ACTIVE)) { + /* wait 1 sec for the DLL State Changed event */ + (void) cv_timedwait(&slot_p->hs_dll_active_cv, + &ctrl_p->hc_mutex, + ddi_get_lbolt() + + SEC_TO_TICK(PCIE_HP_DLL_STATE_CHANGE_TIMEOUT)); + + /* check Link status */ + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + + PCIE_LINKSTS); + if (!(status & PCIE_LINKSTS_DLL_LINK_ACTIVE)) + goto cleanup2; + } + } + + /* wait 1 sec for link to come up */ + delay(drv_usectohz(1000000)); + + /* check power is really turned ON */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + if (control & PCIE_SLOTCTL_PWR_CONTROL) { + PCIE_DBG("slot %d fails to turn on power on connect\n", + slot_p->hs_phy_slot_num); + + goto cleanup1; + } + + /* clear power fault status */ + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + status |= PCIE_SLOTSTS_PWR_FAULT_DETECTED; + pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTSTS, + status); + + /* enable power fault detection interrupt */ + control |= PCIE_SLOTCTL_PWR_FAULT_EN; + pciehpc_issue_hpc_command(ctrl_p, control); + + /* 4. Set power LED to be ON */ + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, PCIE_HP_LED_ON); + + /* if EMI is present, turn it ON */ + if (ctrl_p->hc_has_emi_lock) { + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + + if (!(status & PCIE_SLOTSTS_EMI_LOCK_SET)) { + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + control |= PCIE_SLOTCTL_EMI_LOCK_CONTROL; + pciehpc_issue_hpc_command(ctrl_p, control); + + /* wait 1 sec after toggling the state of EMI lock */ + delay(drv_usectohz(1000000)); + } + } + + *result = slot_p->hs_info.cn_state = + DDI_HP_CN_STATE_POWERED; + + return (DDI_SUCCESS); + +cleanup2: + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + /* if power is ON, set power control to OFF */ + if (!(control & PCIE_SLOTCTL_PWR_CONTROL)) { + control |= PCIE_SLOTCTL_PWR_CONTROL; + pciehpc_issue_hpc_command(ctrl_p, control); + } + +cleanup1: + /* set power led to OFF */ + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, PCIE_HP_LED_OFF); + +cleanup: + return (DDI_FAILURE); +} + +/*ARGSUSED*/ +static int +pciehpc_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result) +{ + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t status, control; + + ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex)); + + /* get the current state of the slot */ + pciehpc_get_slot_state(slot_p); + + /* check if the slot is not in the "enabled' state */ + if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) { + /* slot is in the 'disabled' state */ + PCIE_DBG("pciehpc_slot_poweroff(): " + "slot %d already disabled\n", slot_p->hs_phy_slot_num); + ASSERT(slot_p->hs_power_led_state == PCIE_HP_LED_OFF); + + *result = slot_p->hs_info.cn_state; + return (DDI_SUCCESS); + } + + /* read the Slot Status Register */ + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + + /* make sure the slot has a device present */ + if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) { + /* slot is empty */ + PCIE_DBG("pciehpc_slot_poweroff(): slot %d is empty\n", + slot_p->hs_phy_slot_num); + goto cleanup; + } + + /* + * Disable power to the slot involves: + * 1. Set power LED to blink. + * 2. Set power control OFF in Slot Control Reigster and + * wait for Command Completed Interrupt or 1 sec timeout. + * 3. Set POWER led and ATTN led to be OFF. + */ + + /* 1. set power LED to blink */ + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, PCIE_HP_LED_BLINK); + + /* disable power fault detection interrupt */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + control &= ~PCIE_SLOTCTL_PWR_FAULT_EN; + pciehpc_issue_hpc_command(ctrl_p, control); + + /* 2. set power control to OFF */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + control |= PCIE_SLOTCTL_PWR_CONTROL; + pciehpc_issue_hpc_command(ctrl_p, control); + +#ifdef DEBUG + /* check for power control bit to be OFF */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + ASSERT(control & PCIE_SLOTCTL_PWR_CONTROL); +#endif + + /* 3. Set power LED to be OFF */ + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, PCIE_HP_LED_OFF); + pciehpc_set_led_state(ctrl_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); + + /* if EMI is present, turn it OFF */ + if (ctrl_p->hc_has_emi_lock) { + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + + if (status & PCIE_SLOTSTS_EMI_LOCK_SET) { + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + control |= PCIE_SLOTCTL_EMI_LOCK_CONTROL; + pciehpc_issue_hpc_command(ctrl_p, control); + + /* wait 1 sec after toggling the state of EMI lock */ + delay(drv_usectohz(1000000)); + } + } + + /* get the current state of the slot */ + pciehpc_get_slot_state(slot_p); + + *result = slot_p->hs_info.cn_state; + + return (DDI_SUCCESS); + +cleanup: + return (DDI_FAILURE); +} + +/* + * pciehpc_slot_probe() + * + * Probe the slot. + * + * Note: This function is called by DDI HP framework at kernel context only + */ +/*ARGSUSED*/ +static int +pciehpc_slot_probe(pcie_hp_slot_t *slot_p) +{ + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + int ret = DDI_SUCCESS; + + mutex_enter(&ctrl_p->hc_mutex); + + /* get the current state of the slot */ + pciehpc_get_slot_state(slot_p); + + /* + * Probe a given PCIe Hotplug Connection (CN). + */ + PCIE_DISABLE_ERRORS(ctrl_p->hc_dip); + ret = pcie_hp_probe(slot_p); + + if (ret != DDI_SUCCESS) { + PCIE_DBG("pciehpc_slot_probe() failed\n"); + + /* turn the ATTN led ON for configure failure */ + pciehpc_set_led_state(ctrl_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_ON); + + /* if power to the slot is still on then set Power led to ON */ + if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, + PCIE_HP_LED_ON); + + mutex_exit(&ctrl_p->hc_mutex); + return (DDI_FAILURE); + } + + PCIE_ENABLE_ERRORS(ctrl_p->hc_dip); + + /* get the current state of the slot */ + pciehpc_get_slot_state(slot_p); + + mutex_exit(&ctrl_p->hc_mutex); + return (DDI_SUCCESS); +} + +/* + * pciehpc_slot_unprobe() + * + * Unprobe the slot. + * + * Note: This function is called by DDI HP framework at kernel context only + */ +/*ARGSUSED*/ +static int +pciehpc_slot_unprobe(pcie_hp_slot_t *slot_p) +{ + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + int ret; + + mutex_enter(&ctrl_p->hc_mutex); + + /* get the current state of the slot */ + pciehpc_get_slot_state(slot_p); + + /* + * Unprobe a given PCIe Hotplug Connection (CN). + */ + PCIE_DISABLE_ERRORS(ctrl_p->hc_dip); + ret = pcie_hp_unprobe(slot_p); + + if (ret != DDI_SUCCESS) { + PCIE_DBG("pciehpc_slot_unprobe() failed\n"); + + /* if power to the slot is still on then set Power led to ON */ + if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, + PCIE_HP_LED_ON); + + PCIE_ENABLE_ERRORS(ctrl_p->hc_dip); + + mutex_exit(&ctrl_p->hc_mutex); + return (DDI_FAILURE); + } + + /* get the current state of the slot */ + pciehpc_get_slot_state(slot_p); + + mutex_exit(&ctrl_p->hc_mutex); + return (DDI_SUCCESS); +} + +static int +pciehpc_upgrade_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state; + int rv = DDI_SUCCESS; + + if (target_state > DDI_HP_CN_STATE_ENABLED) { + return (DDI_EINVAL); + } + + curr_state = slot_p->hs_info.cn_state; + while ((curr_state < target_state) && (rv == DDI_SUCCESS)) { + + switch (curr_state) { + case DDI_HP_CN_STATE_EMPTY: + /* + * From EMPTY to PRESENT, just check the hardware + * slot state. + */ + pciehpc_get_slot_state(slot_p); + curr_state = slot_p->hs_info.cn_state; + if (curr_state < DDI_HP_CN_STATE_PRESENT) + rv = DDI_FAILURE; + break; + case DDI_HP_CN_STATE_PRESENT: + rv = (slot_p->hs_ctrl->hc_ops.poweron_hpc_slot)(slot_p, + &curr_state); + + break; + case DDI_HP_CN_STATE_POWERED: + curr_state = slot_p->hs_info.cn_state = + DDI_HP_CN_STATE_ENABLED; + break; + default: + /* should never reach here */ + ASSERT("unknown devinfo state"); + } + } + + return (rv); +} + +static int +pciehpc_downgrade_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state; + int rv = DDI_SUCCESS; + + + curr_state = slot_p->hs_info.cn_state; + while ((curr_state > target_state) && (rv == DDI_SUCCESS)) { + + switch (curr_state) { + case DDI_HP_CN_STATE_PRESENT: + /* + * From PRESENT to EMPTY, just check hardware slot + * state. + */ + pciehpc_get_slot_state(slot_p); + curr_state = slot_p->hs_info.cn_state; + if (curr_state >= DDI_HP_CN_STATE_PRESENT) + rv = DDI_FAILURE; + break; + case DDI_HP_CN_STATE_POWERED: + rv = (slot_p->hs_ctrl->hc_ops.poweroff_hpc_slot)( + slot_p, &curr_state); + + break; + case DDI_HP_CN_STATE_ENABLED: + curr_state = slot_p->hs_info.cn_state = + DDI_HP_CN_STATE_POWERED; + + break; + default: + /* should never reach here */ + ASSERT("unknown devinfo state"); + } + } + + return (rv); +} + +/* Change slot state to a target state */ +static int +pciehpc_change_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state; + int rv; + + pciehpc_get_slot_state(slot_p); + curr_state = slot_p->hs_info.cn_state; + + if (curr_state == target_state) { + return (DDI_SUCCESS); + } + if (curr_state < target_state) { + + rv = pciehpc_upgrade_slot_state(slot_p, target_state); + } else { + rv = pciehpc_downgrade_slot_state(slot_p, target_state); + } + + return (rv); +} + +int +pciehpc_slot_get_property(pcie_hp_slot_t *slot_p, ddi_hp_property_t *arg, + ddi_hp_property_t *rval) +{ + ddi_hp_property_t request, result; +#ifdef _SYSCALL32_IMPL + ddi_hp_property32_t request32, result32; +#endif + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + nvlist_t *prop_list; + nvlist_t *prop_rlist; /* nvlist for return values */ + nvpair_t *prop_pair; + char *name, *value; + int ret = DDI_SUCCESS; + int i, n; + boolean_t get_all_prop = B_FALSE; + + if (get_udatamodel() == DATAMODEL_NATIVE) { + if (copyin(arg, &request, sizeof (ddi_hp_property_t)) || + copyin(rval, &result, sizeof (ddi_hp_property_t))) + return (DDI_FAILURE); + } +#ifdef _SYSCALL32_IMPL + else { + bzero(&request, sizeof (request)); + bzero(&result, sizeof (result)); + if (copyin(arg, &request32, sizeof (ddi_hp_property32_t)) || + copyin(rval, &result32, sizeof (ddi_hp_property32_t))) + return (DDI_FAILURE); + request.nvlist_buf = (char *)(uintptr_t)request32.nvlist_buf; + request.buf_size = request32.buf_size; + result.nvlist_buf = (char *)(uintptr_t)result32.nvlist_buf; + result.buf_size = result32.buf_size; + } +#endif + + if ((ret = pcie_copyin_nvlist(request.nvlist_buf, request.buf_size, + &prop_list)) != DDI_SUCCESS) + return (ret); + + if (nvlist_alloc(&prop_rlist, NV_UNIQUE_NAME, 0)) { + ret = DDI_ENOMEM; + goto get_prop_cleanup; + } + + /* check whether the requested property is "all" or "help" */ + prop_pair = nvlist_next_nvpair(prop_list, NULL); + if (prop_pair && !nvlist_next_nvpair(prop_list, prop_pair)) { + name = nvpair_name(prop_pair); + n = sizeof (pciehpc_props) / sizeof (pciehpc_prop_t); + + if (strcmp(name, PCIEHPC_PROP_ALL) == 0) { + (void) nvlist_remove_all(prop_list, PCIEHPC_PROP_ALL); + + /* + * Add all properties into the request list, so that we + * will get the values in the following for loop. + */ + for (i = 0; i < n; i++) { + if (nvlist_add_string(prop_list, + pciehpc_props[i].prop_name, "") != 0) { + ret = DDI_FAILURE; + goto get_prop_cleanup1; + } + } + get_all_prop = B_TRUE; + } else if (strcmp(name, PCIEHPC_PROP_HELP) == 0) { + /* + * Empty the request list, and add help strings into the + * return list. We will pass the following for loop. + */ + (void) nvlist_remove_all(prop_list, PCIEHPC_PROP_HELP); + + for (i = 0; i < n; i++) { + if (nvlist_add_string(prop_rlist, + pciehpc_props[i].prop_name, + pciehpc_props[i].prop_value) != 0) { + ret = DDI_FAILURE; + goto get_prop_cleanup1; + } + } + } + } + + mutex_enter(&ctrl_p->hc_mutex); + + /* get the current slot state */ + pciehpc_get_slot_state(slot_p); + + /* for each requested property, get the value and add it to nvlist */ + prop_pair = NULL; + while (prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) { + name = nvpair_name(prop_pair); + + if (strcmp(name, PCIEHPC_PROP_LED_FAULT) == 0) { + value = pcie_led_state_text( + slot_p->hs_fault_led_state); + } else if (strcmp(name, PCIEHPC_PROP_LED_POWER) == 0) { + value = pcie_led_state_text( + slot_p->hs_power_led_state); + } else if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { + value = pcie_led_state_text( + slot_p->hs_attn_led_state); + } else if (strcmp(name, PCIEHPC_PROP_LED_ACTIVE) == 0) { + value = pcie_led_state_text( + slot_p->hs_active_led_state); + } else if (strcmp(name, PCIEHPC_PROP_CARD_TYPE) == 0) { + ddi_acc_handle_t handle; + dev_info_t *cdip; + uint8_t prog_class, base_class, sub_class; + int i; + + mutex_exit(&ctrl_p->hc_mutex); + cdip = pcie_hp_devi_find( + ctrl_p->hc_dip, slot_p->hs_device_num, 0); + mutex_enter(&ctrl_p->hc_mutex); + + if ((slot_p->hs_info.cn_state + != DDI_HP_CN_STATE_ENABLED) || (cdip == NULL)) { + /* + * When getting all properties, just ignore the + * one that's not available under certain state. + */ + if (get_all_prop) + continue; + + ret = DDI_ENOTSUP; + goto get_prop_cleanup2; + } + + if (pci_config_setup(cdip, &handle) != DDI_SUCCESS) { + ret = DDI_FAILURE; + goto get_prop_cleanup2; + } + + prog_class = pci_config_get8(handle, + PCI_CONF_PROGCLASS); + base_class = pci_config_get8(handle, PCI_CONF_BASCLASS); + sub_class = pci_config_get8(handle, PCI_CONF_SUBCLASS); + pci_config_teardown(&handle); + + for (i = 0; i < class_pci_items; i++) { + if ((base_class == class_pci[i].base_class) && + (sub_class == class_pci[i].sub_class) && + (prog_class == class_pci[i].prog_class)) { + value = class_pci[i].short_desc; + break; + } + } + if (i == class_pci_items) + value = PCIEHPC_PROP_VALUE_UNKNOWN; + } else if (strcmp(name, PCIEHPC_PROP_BOARD_TYPE) == 0) { + if (slot_p->hs_info.cn_state <= DDI_HP_CN_STATE_EMPTY) + value = PCIEHPC_PROP_VALUE_UNKNOWN; + else + value = PCIEHPC_PROP_VALUE_PCIHOTPLUG; + } else if (strcmp(name, PCIEHPC_PROP_SLOT_CONDITION) == 0) { + value = pcie_slot_condition_text(slot_p->hs_condition); + } else { + /* unsupported property */ + cmn_err(CE_WARN, "Unsupported property: %s\n", name); + + ret = DDI_ENOTSUP; + goto get_prop_cleanup2; + } + if (nvlist_add_string(prop_rlist, name, value) != 0) { + ret = DDI_FAILURE; + goto get_prop_cleanup2; + } + } + + /* pack nvlist and copyout */ + if ((ret = pcie_copyout_nvlist(prop_rlist, result.nvlist_buf, + &result.buf_size)) != DDI_SUCCESS) { + goto get_prop_cleanup2; + } + if (get_udatamodel() == DATAMODEL_NATIVE) { + if (copyout(&result, rval, sizeof (ddi_hp_property_t))) + ret = DDI_FAILURE; + } +#ifdef _SYSCALL32_IMPL + else { + if (result.buf_size > UINT32_MAX) { + ret = DDI_FAILURE; + } else { + result32.buf_size = (uint32_t)result.buf_size; + if (copyout(&result32, rval, + sizeof (ddi_hp_property32_t))) + ret = DDI_FAILURE; + } + } +#endif + +get_prop_cleanup2: + mutex_exit(&ctrl_p->hc_mutex); +get_prop_cleanup1: + nvlist_free(prop_rlist); +get_prop_cleanup: + nvlist_free(prop_list); + return (ret); +} + +int +pciehpc_slot_set_property(pcie_hp_slot_t *slot_p, ddi_hp_property_t *arg, + ddi_hp_property_t *rval) +{ + ddi_hp_property_t request, result; +#ifdef _SYSCALL32_IMPL + ddi_hp_property32_t request32, result32; +#endif + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + nvlist_t *prop_list; + nvlist_t *prop_rlist; + nvpair_t *prop_pair; + char *name, *value; + pcie_hp_led_state_t led_state; + int ret = DDI_SUCCESS; + + if (get_udatamodel() == DATAMODEL_NATIVE) { + if (copyin(arg, &request, sizeof (ddi_hp_property_t))) + return (DDI_FAILURE); + if (rval && + copyin(rval, &result, sizeof (ddi_hp_property_t))) + return (DDI_FAILURE); + } +#ifdef _SYSCALL32_IMPL + else { + bzero(&request, sizeof (request)); + bzero(&result, sizeof (result)); + if (copyin(arg, &request32, sizeof (ddi_hp_property32_t))) + return (DDI_FAILURE); + if (rval && + copyin(rval, &result32, sizeof (ddi_hp_property32_t))) + return (DDI_FAILURE); + request.nvlist_buf = (char *)(uintptr_t)request32.nvlist_buf; + request.buf_size = request32.buf_size; + if (rval) { + result.nvlist_buf = + (char *)(uintptr_t)result32.nvlist_buf; + result.buf_size = result32.buf_size; + } + } +#endif + + if ((ret = pcie_copyin_nvlist(request.nvlist_buf, request.buf_size, + &prop_list)) != DDI_SUCCESS) + return (ret); + + /* check whether the requested property is "help" */ + prop_pair = nvlist_next_nvpair(prop_list, NULL); + if (prop_pair && !nvlist_next_nvpair(prop_list, prop_pair) && + (strcmp(nvpair_name(prop_pair), PCIEHPC_PROP_HELP) == 0)) { + if (!rval) { + ret = DDI_ENOTSUP; + goto set_prop_cleanup; + } + + if (nvlist_alloc(&prop_rlist, NV_UNIQUE_NAME, 0)) { + ret = DDI_ENOMEM; + goto set_prop_cleanup; + } + if (nvlist_add_string(prop_rlist, PCIEHPC_PROP_LED_ATTN, + PCIEHPC_PROP_VALUE_LED) != 0) { + ret = DDI_FAILURE; + goto set_prop_cleanup1; + } + + if ((ret = pcie_copyout_nvlist(prop_rlist, result.nvlist_buf, + &result.buf_size)) != DDI_SUCCESS) { + goto set_prop_cleanup1; + } + if (get_udatamodel() == DATAMODEL_NATIVE) { + if (copyout(&result, rval, + sizeof (ddi_hp_property_t))) { + ret = DDI_FAILURE; + goto set_prop_cleanup1; + } + } +#ifdef _SYSCALL32_IMPL + else { + if (result.buf_size > UINT32_MAX) { + ret = DDI_FAILURE; + goto set_prop_cleanup1; + } else { + result32.buf_size = (uint32_t)result.buf_size; + if (copyout(&result32, rval, + sizeof (ddi_hp_property32_t))) { + ret = DDI_FAILURE; + goto set_prop_cleanup1; + } + } + } +#endif +set_prop_cleanup1: + nvlist_free(prop_rlist); + nvlist_free(prop_list); + return (ret); + } + + /* Validate the request */ + prop_pair = NULL; + while (prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) { + name = nvpair_name(prop_pair); + if (nvpair_type(prop_pair) != DATA_TYPE_STRING) { + cmn_err(CE_WARN, "Unexpected data type of setting " + "property %s.\n", name); + ret = DDI_EINVAL; + goto set_prop_cleanup; + } + if (nvpair_value_string(prop_pair, &value)) { + cmn_err(CE_WARN, "Get string value failed for property " + "%s.\n", name); + ret = DDI_FAILURE; + goto set_prop_cleanup; + } + + if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { + if ((strcmp(value, PCIEHPC_PROP_VALUE_ON) != 0) && + (strcmp(value, PCIEHPC_PROP_VALUE_OFF) != 0) && + (strcmp(value, PCIEHPC_PROP_VALUE_BLINK) != 0)) { + cmn_err(CE_WARN, "Unsupported value of setting " + "property %s\n", name); + ret = DDI_ENOTSUP; + goto set_prop_cleanup; + } + } else { + cmn_err(CE_WARN, "Unsupported property: %s\n", name); + ret = DDI_ENOTSUP; + goto set_prop_cleanup; + } + } + mutex_enter(&ctrl_p->hc_mutex); + + /* get the current slot state */ + pciehpc_get_slot_state(slot_p); + + /* set each property */ + prop_pair = NULL; + while (prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) { + name = nvpair_name(prop_pair); + + if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { + if (strcmp(value, PCIEHPC_PROP_VALUE_ON) == 0) + led_state = PCIE_HP_LED_ON; + else if (strcmp(value, PCIEHPC_PROP_VALUE_OFF) == 0) + led_state = PCIE_HP_LED_OFF; + else if (strcmp(value, PCIEHPC_PROP_VALUE_BLINK) == 0) + led_state = PCIE_HP_LED_BLINK; + + pciehpc_set_led_state(ctrl_p, PCIE_HP_ATTN_LED, + led_state); + } + } + + mutex_exit(&ctrl_p->hc_mutex); +set_prop_cleanup: + nvlist_free(prop_list); + return (ret); +} + +/* + * Send a command to the PCI-E Hot Plug Controller. + * + * NOTES: The PCI-E spec defines the following semantics for issuing hot plug + * commands. + * 1) If Command Complete events/interrupts are supported then software + * waits for Command Complete event after issuing a command (i.e writing + * to the Slot Control register). The command completion could take as + * long as 1 second so software should be prepared to wait for 1 second + * before issuing another command. + * + * 2) If Command Complete events/interrupts are not supported then + * software could issue multiple Slot Control writes without any delay + * between writes. + */ +static void +pciehpc_issue_hpc_command(pcie_hp_ctrl_t *ctrl_p, uint16_t control) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t status; + uint32_t slot_cap; + + /* + * PCI-E version 1.1 spec defines No Command Completed + * Support bit (bit#18) in Slot Capabilities register. If this + * bit is set then slot doesn't support notification of command + * completion events. + */ + slot_cap = pciehpc_reg_get32(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCAP); + + /* + * If no Command Completion event is supported or it is ACPI + * hot plug mode then just issue the command and return. + */ + if ((slot_cap & PCIE_SLOTCAP_NO_CMD_COMP_SUPP) || + (bus_p->bus_hp_curr_mode == PCIE_ACPI_HP_MODE)) { + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL, control); + return; + } + + /* + * ************************************** + * Command Complete events are supported. + * ************************************** + */ + + /* + * If HPC is not yet initialized then just poll for the Command + * Completion interrupt. + */ + if (!(ctrl_p->hc_flags & PCIE_HP_INITIALIZED_FLAG)) { + int retry = PCIE_HP_CMD_WAIT_RETRY; + + /* write the command to the HPC */ + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL, control); + + /* poll for status completion */ + while (retry--) { + /* wait for 10 msec before checking the status */ + delay(drv_usectohz(PCIE_HP_CMD_WAIT_TIME)); + + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + + if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) { + /* clear the status bits */ + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS, status); + break; + } + } + return; + } + + /* HPC is already initialized */ + + ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex)); + + /* + * If previous command is still pending then wait for its + * completion. i.e cv_wait() + */ + + while (ctrl_p->hc_cmd_pending == B_TRUE) + cv_wait(&ctrl_p->hc_cmd_comp_cv, &ctrl_p->hc_mutex); + + /* + * Issue the command and wait for Command Completion or + * the 1 sec timeout. + */ + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL, control); + + ctrl_p->hc_cmd_pending = B_TRUE; + + if (cv_timedwait(&ctrl_p->hc_cmd_comp_cv, &ctrl_p->hc_mutex, + ddi_get_lbolt() + SEC_TO_TICK(1)) == -1) { + + /* it is a timeout */ + PCIE_DBG("pciehpc_issue_hpc_command: Command Complete" + " interrupt is not received for slot %d\n", + slot_p->hs_phy_slot_num); + + /* clear the status info in case interrupts are disabled? */ + status = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS); + + if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) { + /* clear the status bits */ + pciehpc_reg_put16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTSTS, status); + } + } + + ctrl_p->hc_cmd_pending = B_FALSE; + + /* wake up any one waiting for issuing another command to HPC */ + cv_signal(&ctrl_p->hc_cmd_comp_cv); +} + +/* + * pciehcp_attn_btn_handler() + * + * This handles ATTN button pressed event as per the PCI-E 1.1 spec. + */ +static void +pciehpc_attn_btn_handler(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + pcie_hp_led_state_t power_led_state; + callb_cpr_t cprinfo; + + PCIE_DBG("pciehpc_attn_btn_handler: thread started\n"); + + CALLB_CPR_INIT(&cprinfo, &ctrl_p->hc_mutex, callb_generic_cpr, + "pciehpc_attn_btn_handler"); + + mutex_enter(&ctrl_p->hc_mutex); + + /* wait for ATTN button event */ + cv_wait(&slot_p->hs_attn_btn_cv, &ctrl_p->hc_mutex); + + while (slot_p->hs_attn_btn_thread_exit == B_FALSE) { + if (slot_p->hs_attn_btn_pending == B_TRUE) { + /* get the current state of power LED */ + power_led_state = pciehpc_get_led_state(ctrl_p, + PCIE_HP_POWER_LED); + + /* Blink the Power LED while we wait for 5 seconds */ + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, + PCIE_HP_LED_BLINK); + + /* wait for 5 seconds before taking any action */ + if (cv_timedwait(&slot_p->hs_attn_btn_cv, + &ctrl_p->hc_mutex, + ddi_get_lbolt() + SEC_TO_TICK(5)) == -1) { + /* + * It is a time out; make sure the ATTN pending + * flag is still ON before sending the event to + * DDI HP framework. + */ + if (slot_p->hs_attn_btn_pending == B_TRUE) { + int hint; + + slot_p->hs_attn_btn_pending = B_FALSE; + pciehpc_get_slot_state(slot_p); + + if (slot_p->hs_info.cn_state <= + DDI_HP_CN_STATE_PRESENT) { + /* + * Insertion. + */ + hint = SE_INCOMING_RES; + } else { + /* + * Want to remove; + */ + hint = SE_OUTGOING_RES; + } + + /* + * We can't call ddihp_cn_gen_sysevent + * here since it's not a DDI interface. + */ + pcie_hp_gen_sysevent_req( + slot_p->hs_info.cn_name, + hint, + ctrl_p->hc_dip, + KM_SLEEP); + } + } + + /* restore the power LED state */ + pciehpc_set_led_state(ctrl_p, PCIE_HP_POWER_LED, + power_led_state); + continue; + } + + /* wait for another ATTN button event */ + cv_wait(&slot_p->hs_attn_btn_cv, &ctrl_p->hc_mutex); + } + + PCIE_DBG("pciehpc_attn_btn_handler: thread exit\n"); + cv_signal(&slot_p->hs_attn_btn_cv); + CALLB_CPR_EXIT(&cprinfo); + thread_exit(); +} + +/* + * convert LED state from PCIE HPC definition to pcie_hp_led_state_t + * definition. + */ +static pcie_hp_led_state_t +pciehpc_led_state_to_hpc(uint16_t state) +{ + switch (state) { + case PCIE_SLOTCTL_INDICATOR_STATE_ON: + return (PCIE_HP_LED_ON); + case PCIE_SLOTCTL_INDICATOR_STATE_BLINK: + return (PCIE_HP_LED_BLINK); + case PCIE_SLOTCTL_INDICATOR_STATE_OFF: + default: + return (PCIE_HP_LED_OFF); + } +} + +/* + * Get the state of an LED. + */ +static pcie_hp_led_state_t +pciehpc_get_led_state(pcie_hp_ctrl_t *ctrl_p, pcie_hp_led_t led) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t control, state; + + /* get the current state of Slot Control register */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + switch (led) { + case PCIE_HP_POWER_LED: + state = pcie_slotctl_pwr_indicator_get(control); + break; + case PCIE_HP_ATTN_LED: + state = pcie_slotctl_attn_indicator_get(control); + break; + default: + PCIE_DBG("pciehpc_get_led_state() invalid LED %d\n", led); + return (PCIE_HP_LED_OFF); + } + + switch (state) { + case PCIE_SLOTCTL_INDICATOR_STATE_ON: + return (PCIE_HP_LED_ON); + + case PCIE_SLOTCTL_INDICATOR_STATE_BLINK: + return (PCIE_HP_LED_BLINK); + + case PCIE_SLOTCTL_INDICATOR_STATE_OFF: + default: + return (PCIE_HP_LED_OFF); + } +} + +/* + * Set the state of an LED. It updates both hw and sw state. + */ +static void +pciehpc_set_led_state(pcie_hp_ctrl_t *ctrl_p, pcie_hp_led_t led, + pcie_hp_led_state_t state) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t control; + + /* get the current state of Slot Control register */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + switch (led) { + case PCIE_HP_POWER_LED: + /* clear led mask */ + control &= ~PCIE_SLOTCTL_PWR_INDICATOR_MASK; + slot_p->hs_power_led_state = state; + break; + case PCIE_HP_ATTN_LED: + /* clear led mask */ + control &= ~PCIE_SLOTCTL_ATTN_INDICATOR_MASK; + slot_p->hs_attn_led_state = state; + break; + default: + PCIE_DBG("pciehpc_set_led_state() invalid LED %d\n", led); + return; + } + + switch (state) { + case PCIE_HP_LED_ON: + if (led == PCIE_HP_POWER_LED) + control = pcie_slotctl_pwr_indicator_set(control, + PCIE_SLOTCTL_INDICATOR_STATE_ON); + else if (led == PCIE_HP_ATTN_LED) + control = pcie_slotctl_attn_indicator_set(control, + PCIE_SLOTCTL_INDICATOR_STATE_ON); + break; + case PCIE_HP_LED_OFF: + if (led == PCIE_HP_POWER_LED) + control = pcie_slotctl_pwr_indicator_set(control, + PCIE_SLOTCTL_INDICATOR_STATE_OFF); + else if (led == PCIE_HP_ATTN_LED) + control = pcie_slotctl_attn_indicator_set(control, + PCIE_SLOTCTL_INDICATOR_STATE_OFF); + break; + case PCIE_HP_LED_BLINK: + if (led == PCIE_HP_POWER_LED) + control = pcie_slotctl_pwr_indicator_set(control, + PCIE_SLOTCTL_INDICATOR_STATE_BLINK); + else if (led == PCIE_HP_ATTN_LED) + control = pcie_slotctl_attn_indicator_set(control, + PCIE_SLOTCTL_INDICATOR_STATE_BLINK); + break; + + default: + PCIE_DBG("pciehpc_set_led_state() invalid LED state %d\n", + state); + return; + } + + /* update the Slot Control Register */ + pciehpc_issue_hpc_command(ctrl_p, control); + +#ifdef DEBUG + /* get the current state of Slot Control register */ + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + PCIE_DBG("pciehpc_set_led_state: slot %d power-led %s attn-led %s\n", + slot_p->hs_phy_slot_num, pcie_led_state_text( + pciehpc_led_state_to_hpc(pcie_slotctl_pwr_indicator_get(control))), + pcie_led_state_text(pciehpc_led_state_to_hpc( + pcie_slotctl_attn_indicator_get(control)))); +#endif +} + +#ifdef DEBUG +/* + * Dump PCI-E Hot Plug registers. + */ +static void +pciehpc_dump_hpregs(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uint16_t control; + uint32_t capabilities; + + if (!pcie_debug_flags) + return; + + capabilities = pciehpc_reg_get32(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCAP); + + control = pciehpc_reg_get16(ctrl_p, + bus_p->bus_pcie_off + PCIE_SLOTCTL); + + PCIE_DBG("pciehpc_dump_hpregs: Found PCI-E hot plug slot %d\n", + slot_p->hs_phy_slot_num); + + PCIE_DBG("Attention Button Present = %s\n", + capabilities & PCIE_SLOTCAP_ATTN_BUTTON ? "Yes":"No"); + + PCIE_DBG("Power controller Present = %s\n", + capabilities & PCIE_SLOTCAP_POWER_CONTROLLER ? "Yes":"No"); + + PCIE_DBG("MRL Sensor Present = %s\n", + capabilities & PCIE_SLOTCAP_MRL_SENSOR ? "Yes":"No"); + + PCIE_DBG("Attn Indicator Present = %s\n", + capabilities & PCIE_SLOTCAP_ATTN_INDICATOR ? "Yes":"No"); + + PCIE_DBG("Power Indicator Present = %s\n", + capabilities & PCIE_SLOTCAP_PWR_INDICATOR ? "Yes":"No"); + + PCIE_DBG("HotPlug Surprise = %s\n", + capabilities & PCIE_SLOTCAP_HP_SURPRISE ? "Yes":"No"); + + PCIE_DBG("HotPlug Capable = %s\n", + capabilities & PCIE_SLOTCAP_HP_CAPABLE ? "Yes":"No"); + + PCIE_DBG("Physical Slot Number = %d\n", + PCIE_SLOTCAP_PHY_SLOT_NUM(capabilities)); + + PCIE_DBG("Attn Button interrupt Enabled = %s\n", + control & PCIE_SLOTCTL_ATTN_BTN_EN ? "Yes":"No"); + + PCIE_DBG("Power Fault interrupt Enabled = %s\n", + control & PCIE_SLOTCTL_PWR_FAULT_EN ? "Yes":"No"); + + PCIE_DBG("MRL Sensor INTR Enabled = %s\n", + control & PCIE_SLOTCTL_MRL_SENSOR_EN ? "Yes":"No"); + + PCIE_DBG("Presence interrupt Enabled = %s\n", + control & PCIE_SLOTCTL_PRESENCE_CHANGE_EN ? "Yes":"No"); + + PCIE_DBG("Cmd Complete interrupt Enabled = %s\n", + control & PCIE_SLOTCTL_CMD_INTR_EN ? "Yes":"No"); + + PCIE_DBG("HotPlug interrupt Enabled = %s\n", + control & PCIE_SLOTCTL_HP_INTR_EN ? "Yes":"No"); + + PCIE_DBG("Power Indicator LED = %s", pcie_led_state_text( + pciehpc_led_state_to_hpc(pcie_slotctl_pwr_indicator_get(control)))); + + PCIE_DBG("Attn Indicator LED = %s\n", + pcie_led_state_text(pciehpc_led_state_to_hpc( + pcie_slotctl_attn_indicator_get(control)))); +} +#endif /* DEBUG */ diff --git a/usr/src/uts/common/io/pciex/hotplug/pcishpc.c b/usr/src/uts/common/io/pciex/hotplug/pcishpc.c new file mode 100644 index 0000000000..40c6f71a46 --- /dev/null +++ b/usr/src/uts/common/io/pciex/hotplug/pcishpc.c @@ -0,0 +1,2645 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * This file contains PCI HotPlug functionality that is compatible with the + * PCI SHPC specification 1.x. + * + * NOTE: This file is compiled and delivered through misc/pcie module. + */ + +#include <sys/note.h> +#include <sys/conf.h> +#include <sys/kmem.h> +#include <sys/kstat.h> +#include <sys/debug.h> +#include <sys/vtrace.h> +#include <sys/autoconf.h> +#include <sys/varargs.h> +#include <sys/hwconf.h> +#include <sys/ddi_impldefs.h> +#include <sys/callb.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/sysevent/dr.h> +#include <sys/ndi_impldefs.h> +#include <sys/pci_impl.h> +#include <sys/hotplug/pci/pcie_hp.h> +#include <sys/hotplug/pci/pcishpc.h> + +typedef struct pcishpc_prop { + char *prop_name; + char *prop_value; +} pcishpc_prop_t; + +static pcishpc_prop_t pcishpc_props[] = { + { PCIEHPC_PROP_LED_FAULT, PCIEHPC_PROP_VALUE_LED }, + { PCIEHPC_PROP_LED_POWER, PCIEHPC_PROP_VALUE_LED }, + { PCIEHPC_PROP_LED_ATTN, PCIEHPC_PROP_VALUE_LED }, + { PCIEHPC_PROP_LED_ACTIVE, PCIEHPC_PROP_VALUE_LED }, + { PCIEHPC_PROP_CARD_TYPE, PCIEHPC_PROP_VALUE_TYPE }, + { PCIEHPC_PROP_BOARD_TYPE, PCIEHPC_PROP_VALUE_TYPE }, + { PCIEHPC_PROP_SLOT_CONDITION, PCIEHPC_PROP_VALUE_TYPE } +}; + +/* reset delay to 1 sec. */ +static int pcishpc_reset_delay = 1000000; + +/* Local function prototype */ +static pcie_hp_ctrl_t *pcishpc_create_controller(dev_info_t *dip); +static int pcishpc_setup_controller(pcie_hp_ctrl_t *ctrl_p); +static int pcishpc_destroy_controller(dev_info_t *dip); +static pcie_hp_slot_t *pcishpc_create_slot(pcie_hp_ctrl_t *ctrl_p); +static int pcishpc_register_slot(pcie_hp_ctrl_t *ctrl_p, int slot); +static int pcishpc_destroy_slots(pcie_hp_ctrl_t *ctrl_p); +static int pcishpc_enable_irqs(pcie_hp_ctrl_t *ctrl_p); +static int pcishpc_disable_irqs(pcie_hp_ctrl_t *ctrl_p); +static int pcishpc_slot_get_property(pcie_hp_slot_t *slot_p, + ddi_hp_property_t *arg, ddi_hp_property_t *rval); +static int pcishpc_slot_set_property(pcie_hp_slot_t *slot_p, + ddi_hp_property_t *arg, ddi_hp_property_t *rval); +static int pcishpc_issue_command(pcie_hp_ctrl_t *ctrl_p, + uint32_t cmd_code); +static int pcishpc_wait_busy(pcie_hp_ctrl_t *ctrl_p); +static void pcishpc_attn_btn_handler(pcie_hp_slot_t *slot_p); +static void pcishpc_get_slot_state(pcie_hp_slot_t *slot_p); +static int pcishpc_set_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t new_slot_state); +static void pcishpc_set_slot_name(pcie_hp_ctrl_t *ctrl_p, int slot); +static int pcishpc_set_bus_speed(pcie_hp_slot_t *slot_p); +static int pcishpc_setled(pcie_hp_slot_t *slot_p, pcie_hp_led_t led, + pcie_hp_led_state_t state); +static int pcishpc_led_shpc_to_hpc(int state); +static int pcishpc_led_hpc_to_shpc(int state); +static int pcishpc_slot_shpc_to_hpc(int shpc_state); +static int pcishpc_slot_hpc_to_shpc(int state); +static char *pcishpc_slot_textslotstate(ddi_hp_cn_state_t state); +static char *pcishpc_slot_textledstate(pcie_hp_led_state_t state); + +static uint32_t pcishpc_read_reg(pcie_hp_ctrl_t *ctrl_p, int reg); +static void pcishpc_write_reg(pcie_hp_ctrl_t *ctrl_p, int reg, + uint32_t data); + +static int pcishpc_upgrade_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state); +static int pcishpc_downgrade_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state); +static int pcishpc_change_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state); + +static int pcishpc_slot_poweron(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t *result_state); +static int pcishpc_slot_poweroff(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t *result_state); +static int pcishpc_slot_probe(pcie_hp_slot_t *slot_p); +static int pcishpc_slot_unprobe(pcie_hp_slot_t *slot_p); +#ifdef DEBUG +static void pcishpc_dump_regs(pcie_hp_ctrl_t *ctrl_p); +#endif /* DEBUG */ + + +/* + * Global functions (called by other drivers/modules) + */ + +/* + * pcishpc_init() + * + * Install and configure an SHPC controller and register the HotPlug slots + * with the Solaris HotPlug framework. This function is usually called by + * a PCI bridge Nexus driver that has a built in SHPC controller. + */ +int +pcishpc_init(dev_info_t *dip) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + pcie_hp_ctrl_t *ctrl_p; + int i; + + PCIE_DBG("pcishpc_init() called from %s#%d\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) != NULL) { + PCIE_DBG("pcishpc_init() shpc instance already " + "initialized!\n"); + return (DDI_SUCCESS); + } + + /* Initialize soft state structure for the SHPC instance. */ + ctrl_p = pcishpc_create_controller(dip); + + if (ctrl_p == NULL) { + PCIE_DBG("pcishpc_init() failed to create shpc softstate\n"); + return (DDI_FAILURE); + } + + if (pcishpc_setup_controller(ctrl_p) != DDI_SUCCESS) { + PCIE_DBG("pcishpc_init() failed to setup controller\n"); + goto cleanup; + } + + /* + * Setup resource maps for this bus node. + */ + (void) pci_resource_setup(dip); + +#ifdef DEBUG + PCIE_DBG("%s%d: P2P bridge register dump:\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + + for (i = 0; i < 0x100; i += 4) { + PCIE_DBG("SHPC Cfg reg 0x%02x: %08x\n", i, + pci_config_get32(bus_p->bus_cfg_hdl, i)); + } +#endif /* DEBUG */ + + /* Setup each HotPlug slot on this SHPC controller. */ + for (i = 0; i < ctrl_p->hc_num_slots_impl; i++) { + if (pcishpc_register_slot(ctrl_p, i) != DDI_SUCCESS) { + PCIE_DBG("pcishpc_init() failed to register " + "slot %d\n", i); + goto cleanup1; + } + if (pcie_create_minor_node(ctrl_p, i) != DDI_SUCCESS) { + PCIE_DBG("pcishpc_init() failed to create " + "minor node for slot %d\n", i); + goto cleanup1; + } + } + + (void) pcishpc_enable_irqs(ctrl_p); + +#ifdef DEBUG + /* Dump out the SHPC registers. */ + pcishpc_dump_regs(ctrl_p); +#endif /* DEBUG */ + + PCIE_DBG("pcishpc_init() success(dip=%p)\n", dip); + return (DDI_SUCCESS); + +cleanup1: + for (i = 0; i < ctrl_p->hc_num_slots_impl; i++) { + if (ctrl_p->hc_slots[i] == NULL) + continue; + + pcie_remove_minor_node(ctrl_p, i); + } + (void) pci_resource_destroy(dip); +cleanup: + (void) pcishpc_destroy_controller(dip); + return (DDI_FAILURE); +} + +/* + * pcishpc_uninit() + * Unload the HogPlug controller driver and deallocate all resources. + */ +int +pcishpc_uninit(dev_info_t *dip) +{ + pcie_hp_ctrl_t *ctrl_p; + int i; + + PCIE_DBG("pcishpc_uninit() called(dip=%p)\n", dip); + + ctrl_p = PCIE_GET_HP_CTRL(dip); + + if (!ctrl_p) { + PCIE_DBG("pcishpc_uninit() Unable to find softstate\n"); + return (DDI_FAILURE); + } + + for (i = 0; i < PCIE_HP_MAX_SLOTS; i++) { + if (ctrl_p->hc_slots[i] == NULL) + continue; + + pcie_remove_minor_node(ctrl_p, i); + } + + (void) pcishpc_disable_irqs(ctrl_p); + ctrl_p->hc_flags = 0; + + /* + * Destroy resource maps for this bus node. + */ + (void) pci_resource_destroy(dip); + + (void) pcishpc_destroy_controller(dip); + + PCIE_DBG("pcishpc_uninit() success(dip=%p)\n", dip); + + return (DDI_SUCCESS); +} + +/* + * pcishpc_intr() + * + * This is the SHPC controller interrupt handler. + */ +int +pcishpc_intr(dev_info_t *dip) +{ + pcie_hp_ctrl_t *ctrl_p; + uint32_t irq_locator, irq_serr_locator, reg; + int slot; + + PCIE_DBG("pcishpc_intr() called\n"); + + /* get the soft state structure for this dip */ + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) + return (DDI_INTR_UNCLAIMED); + + mutex_enter(&ctrl_p->hc_mutex); + + if (!(ctrl_p->hc_flags & PCIE_HP_INITIALIZED_FLAG)) { + PCIE_DBG("pcishpc_intr() unclaimed\n"); + mutex_exit(&ctrl_p->hc_mutex); + return (DDI_INTR_UNCLAIMED); + } + + PCIE_DBG("pcishpc_intr() interrupt received\n"); + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG); + + if (reg & PCI_HP_SERR_INT_CMD_COMPLETE_IRQ) { + PCIE_DBG("pcishpc_intr() " + "PCI_HP_SERR_INT_CMD_COMPLETE_IRQ detected\n"); + ctrl_p->hc_cmd_pending = B_FALSE; + cv_signal(&ctrl_p->hc_cmd_comp_cv); + } + + if (reg & PCI_HP_SERR_INT_ARBITER_IRQ) { + PCIE_DBG("pcishpc_intr() PCI_HP_SERR_INT_ARBITER_IRQ " + "detected\n"); + ctrl_p->hc_arbiter_timeout = B_TRUE; + } + + /* Write back the SERR INT register to acknowledge the IRQs. */ + pcishpc_write_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG, reg); + + irq_locator = pcishpc_read_reg(ctrl_p, PCI_HP_IRQ_LOCATOR_REG); + irq_serr_locator = pcishpc_read_reg(ctrl_p, PCI_HP_SERR_LOCATOR_REG); + + /* Check for slot events that might have occured. */ + for (slot = 0; slot < ctrl_p->hc_num_slots_impl; slot++) { + if ((irq_locator & (PCI_HP_IRQ_SLOT_N_PENDING<<slot)) || + (irq_serr_locator & + (PCI_HP_IRQ_SERR_SLOT_N_PENDING<<slot))) { + PCIE_DBG("pcishpc_intr() slot %d and " + "pending IRQ\n", slot+1); + + reg = pcishpc_read_reg(ctrl_p, + PCI_HP_LOGICAL_SLOT_REGS+slot); + + if (reg & PCI_HP_SLOT_PRESENCE_DETECTED) + PCIE_DBG("slot %d: " + "PCI_HP_SLOT_PRESENCE_DETECTED\n", + slot+1); + + if (reg & PCI_HP_SLOT_ISO_PWR_DETECTED) + PCIE_DBG("slot %d: " + "PCI_HP_SLOT_ISO_PWR_DETECTED\n", + slot+1); + + if (reg & PCI_HP_SLOT_ATTN_DETECTED) { + PCIE_DBG("slot %d: " + "PCI_HP_SLOT_ATTN_DETECTED\n", slot+1); + + /* + * if ATTN button event is still pending + * then cancel it + */ + if (ctrl_p->hc_slots[slot]-> + hs_attn_btn_pending == B_TRUE) + ctrl_p->hc_slots[slot]-> + hs_attn_btn_pending = B_FALSE; + + /* wake up the ATTN event handler */ + cv_signal(&ctrl_p->hc_slots[slot]-> + hs_attn_btn_cv); + } + + if (reg & PCI_HP_SLOT_MRL_DETECTED) + PCIE_DBG("slot %d: " + "PCI_HP_SLOT_MRL_DETECTED\n", slot+1); + + if (reg & PCI_HP_SLOT_POWER_DETECTED) + PCIE_DBG("slot %d: " + "PCI_HP_SLOT_POWER_DETECTED\n", slot+1); + + /* Acknoledge any slot interrupts */ + pcishpc_write_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot, + reg); + } + } + + mutex_exit(&ctrl_p->hc_mutex); + + PCIE_DBG("pcishpc_intr() claimed\n"); + + return (DDI_INTR_CLAIMED); +} + +int +pcishpc_slot_get_property(pcie_hp_slot_t *slot_p, ddi_hp_property_t *arg, + ddi_hp_property_t *rval) +{ + ddi_hp_property_t request, result; +#ifdef _SYSCALL32_IMPL + ddi_hp_property32_t request32, result32; +#endif + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + nvlist_t *prop_list; + nvlist_t *prop_rlist; /* nvlist for return values */ + nvpair_t *prop_pair; + char *name, *value; + int ret = DDI_SUCCESS; + int i, n; + boolean_t get_all_prop = B_FALSE; + + if (get_udatamodel() == DATAMODEL_NATIVE) { + if (copyin(arg, &request, sizeof (ddi_hp_property_t)) || + copyin(rval, &result, sizeof (ddi_hp_property_t))) + return (DDI_FAILURE); + } +#ifdef _SYSCALL32_IMPL + else { + bzero(&request, sizeof (request)); + bzero(&result, sizeof (result)); + if (copyin(arg, &request32, sizeof (ddi_hp_property32_t)) || + copyin(rval, &result32, sizeof (ddi_hp_property32_t))) + return (DDI_FAILURE); + request.nvlist_buf = (char *)(uintptr_t)request32.nvlist_buf; + request.buf_size = request32.buf_size; + result.nvlist_buf = (char *)(uintptr_t)result32.nvlist_buf; + result.buf_size = result32.buf_size; + } +#endif + + if ((ret = pcie_copyin_nvlist(request.nvlist_buf, request.buf_size, + &prop_list)) != DDI_SUCCESS) + return (ret); + + if (nvlist_alloc(&prop_rlist, NV_UNIQUE_NAME, 0)) { + ret = DDI_ENOMEM; + goto get_prop_cleanup; + } + + /* check whether the requested property is "all" or "help" */ + prop_pair = nvlist_next_nvpair(prop_list, NULL); + if (prop_pair && !nvlist_next_nvpair(prop_list, prop_pair)) { + name = nvpair_name(prop_pair); + n = sizeof (pcishpc_props) / sizeof (pcishpc_prop_t); + + if (strcmp(name, PCIEHPC_PROP_ALL) == 0) { + (void) nvlist_remove_all(prop_list, PCIEHPC_PROP_ALL); + + /* + * Add all properties into the request list, so that we + * will get the values in the following for loop. + */ + for (i = 0; i < n; i++) { + if (nvlist_add_string(prop_list, + pcishpc_props[i].prop_name, "") != 0) { + ret = DDI_FAILURE; + goto get_prop_cleanup1; + } + } + get_all_prop = B_TRUE; + } else if (strcmp(name, PCIEHPC_PROP_HELP) == 0) { + /* + * Empty the request list, and add help strings into the + * return list. We will pass the following for loop. + */ + (void) nvlist_remove_all(prop_list, PCIEHPC_PROP_HELP); + + for (i = 0; i < n; i++) { + if (nvlist_add_string(prop_rlist, + pcishpc_props[i].prop_name, + pcishpc_props[i].prop_value) != 0) { + ret = DDI_FAILURE; + goto get_prop_cleanup1; + } + } + } + } + + mutex_enter(&ctrl_p->hc_mutex); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + /* for each requested property, get the value and add it to nvlist */ + prop_pair = NULL; + while (prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) { + name = nvpair_name(prop_pair); + + if (strcmp(name, PCIEHPC_PROP_LED_FAULT) == 0) { + value = pcie_led_state_text( + slot_p->hs_fault_led_state); + } else if (strcmp(name, PCIEHPC_PROP_LED_POWER) == 0) { + value = pcie_led_state_text( + slot_p->hs_power_led_state); + } else if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { + value = pcie_led_state_text( + slot_p->hs_attn_led_state); + } else if (strcmp(name, PCIEHPC_PROP_LED_ACTIVE) == 0) { + value = pcie_led_state_text( + slot_p->hs_active_led_state); + } else if (strcmp(name, PCIEHPC_PROP_CARD_TYPE) == 0) { + ddi_acc_handle_t handle; + dev_info_t *cdip; + uint8_t prog_class, base_class, sub_class; + int i; + + mutex_exit(&ctrl_p->hc_mutex); + cdip = pcie_hp_devi_find( + ctrl_p->hc_dip, slot_p->hs_device_num, 0); + mutex_enter(&ctrl_p->hc_mutex); + + if ((slot_p->hs_info.cn_state != + DDI_HP_CN_STATE_ENABLED) || (cdip == NULL)) { + /* + * When getting all properties, just ignore the + * one that's not available under certain state. + */ + if (get_all_prop) + continue; + + ret = DDI_ENOTSUP; + goto get_prop_cleanup2; + } + + if (pci_config_setup(cdip, &handle) != DDI_SUCCESS) { + ret = DDI_FAILURE; + goto get_prop_cleanup2; + } + + prog_class = pci_config_get8(handle, + PCI_CONF_PROGCLASS); + base_class = pci_config_get8(handle, PCI_CONF_BASCLASS); + sub_class = pci_config_get8(handle, PCI_CONF_SUBCLASS); + pci_config_teardown(&handle); + + for (i = 0; i < class_pci_items; i++) { + if ((base_class == class_pci[i].base_class) && + (sub_class == class_pci[i].sub_class) && + (prog_class == class_pci[i].prog_class)) { + value = class_pci[i].short_desc; + break; + } + } + if (i == class_pci_items) + value = PCIEHPC_PROP_VALUE_UNKNOWN; + } else if (strcmp(name, PCIEHPC_PROP_BOARD_TYPE) == 0) { + if (slot_p->hs_info.cn_state <= DDI_HP_CN_STATE_EMPTY) + value = PCIEHPC_PROP_VALUE_UNKNOWN; + else + value = PCIEHPC_PROP_VALUE_PCIHOTPLUG; + } else if (strcmp(name, PCIEHPC_PROP_SLOT_CONDITION) == 0) { + value = pcie_slot_condition_text(slot_p->hs_condition); + } else { + /* unsupported property */ + cmn_err(CE_WARN, "Unsupported property: %s\n", name); + + ret = DDI_ENOTSUP; + goto get_prop_cleanup2; + } + if (nvlist_add_string(prop_rlist, name, value) != 0) { + ret = DDI_FAILURE; + goto get_prop_cleanup2; + } + } + + // pack nvlist and copyout + if ((ret = pcie_copyout_nvlist(prop_rlist, result.nvlist_buf, + &result.buf_size)) != DDI_SUCCESS) { + goto get_prop_cleanup2; + } + if (get_udatamodel() == DATAMODEL_NATIVE) { + if (copyout(&result, rval, sizeof (ddi_hp_property_t))) { + ret = DDI_FAILURE; + goto get_prop_cleanup2; + } + } +#ifdef _SYSCALL32_IMPL + else { + if (result.buf_size > UINT32_MAX) { + ret = DDI_FAILURE; + } else { + result32.buf_size = (uint32_t)result.buf_size; + if (copyout(&result32, rval, + sizeof (ddi_hp_property32_t))) + ret = DDI_FAILURE; + } + } +#endif + +get_prop_cleanup2: + mutex_exit(&ctrl_p->hc_mutex); +get_prop_cleanup1: + nvlist_free(prop_rlist); +get_prop_cleanup: + nvlist_free(prop_list); + return (ret); +} + +int +pcishpc_slot_set_property(pcie_hp_slot_t *slot_p, ddi_hp_property_t *arg, + ddi_hp_property_t *rval) +{ + ddi_hp_property_t request, result; +#ifdef _SYSCALL32_IMPL + ddi_hp_property32_t request32, result32; +#endif + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + nvlist_t *prop_list; + nvlist_t *prop_rlist; + nvpair_t *prop_pair; + char *name, *value; + pcie_hp_led_state_t led_state; + int ret = DDI_SUCCESS; + + if (get_udatamodel() == DATAMODEL_NATIVE) { + if (copyin(arg, &request, sizeof (ddi_hp_property_t))) + return (DDI_FAILURE); + if (rval && + copyin(rval, &result, sizeof (ddi_hp_property_t))) + return (DDI_FAILURE); + } +#ifdef _SYSCALL32_IMPL + else { + bzero(&request, sizeof (request)); + bzero(&result, sizeof (result)); + if (copyin(arg, &request32, sizeof (ddi_hp_property32_t))) + return (DDI_FAILURE); + if (rval && + copyin(rval, &result32, sizeof (ddi_hp_property32_t))) + return (DDI_FAILURE); + request.nvlist_buf = (char *)(uintptr_t)request32.nvlist_buf; + request.buf_size = request32.buf_size; + if (rval) { + result.nvlist_buf = + (char *)(uintptr_t)result32.nvlist_buf; + result.buf_size = result32.buf_size; + } + } +#endif + + if ((ret = pcie_copyin_nvlist(request.nvlist_buf, request.buf_size, + &prop_list)) != DDI_SUCCESS) + return (ret); + + /* check whether the requested property is "help" */ + prop_pair = nvlist_next_nvpair(prop_list, NULL); + if (prop_pair && !nvlist_next_nvpair(prop_list, prop_pair) && + (strcmp(nvpair_name(prop_pair), PCIEHPC_PROP_HELP) == 0)) { + if (!rval) { + ret = DDI_ENOTSUP; + goto set_prop_cleanup; + } + + if (nvlist_alloc(&prop_rlist, NV_UNIQUE_NAME, 0)) { + ret = DDI_ENOMEM; + goto set_prop_cleanup; + } + if (nvlist_add_string(prop_rlist, PCIEHPC_PROP_LED_ATTN, + PCIEHPC_PROP_VALUE_LED) != 0) { + ret = DDI_FAILURE; + goto set_prop_cleanup1; + } + + if ((ret = pcie_copyout_nvlist(prop_rlist, result.nvlist_buf, + &result.buf_size)) != DDI_SUCCESS) { + goto set_prop_cleanup1; + } + if (get_udatamodel() == DATAMODEL_NATIVE) { + if (copyout(&result, rval, + sizeof (ddi_hp_property_t))) { + ret = DDI_FAILURE; + goto set_prop_cleanup1; + } + } +#ifdef _SYSCALL32_IMPL + else { + if (result.buf_size > UINT32_MAX) { + ret = DDI_FAILURE; + goto set_prop_cleanup1; + } else { + result32.buf_size = (uint32_t)result.buf_size; + if (copyout(&result32, rval, + sizeof (ddi_hp_property32_t))) { + ret = DDI_FAILURE; + goto set_prop_cleanup1; + } + } + } +#endif +set_prop_cleanup1: + nvlist_free(prop_rlist); + nvlist_free(prop_list); + return (ret); + } + + /* Validate the request */ + prop_pair = NULL; + while (prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) { + name = nvpair_name(prop_pair); + if (nvpair_type(prop_pair) != DATA_TYPE_STRING) { + cmn_err(CE_WARN, "Unexpected data type of setting " + "property %s.\n", name); + ret = DDI_EINVAL; + goto set_prop_cleanup; + } + if (nvpair_value_string(prop_pair, &value)) { + cmn_err(CE_WARN, "Get string value failed for property " + "%s.\n", name); + ret = DDI_FAILURE; + goto set_prop_cleanup; + } + + if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { + if ((strcmp(value, PCIEHPC_PROP_VALUE_ON) != 0) && + (strcmp(value, PCIEHPC_PROP_VALUE_OFF) != 0) && + (strcmp(value, PCIEHPC_PROP_VALUE_BLINK) != 0)) { + cmn_err(CE_WARN, "Unsupported value of setting " + "property %s\n", name); + ret = DDI_ENOTSUP; + goto set_prop_cleanup; + } + } else { + cmn_err(CE_WARN, "Unsupported property: %s\n", name); + ret = DDI_ENOTSUP; + goto set_prop_cleanup; + } + } + + mutex_enter(&ctrl_p->hc_mutex); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + // set each property + prop_pair = NULL; + while (prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) { + name = nvpair_name(prop_pair); + + if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { + if (strcmp(value, PCIEHPC_PROP_VALUE_ON) == 0) + led_state = PCIE_HP_LED_ON; + else if (strcmp(value, PCIEHPC_PROP_VALUE_OFF) == 0) + led_state = PCIE_HP_LED_OFF; + else if (strcmp(value, PCIEHPC_PROP_VALUE_BLINK) == 0) + led_state = PCIE_HP_LED_BLINK; + + (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, + led_state); + } + } + + mutex_exit(&ctrl_p->hc_mutex); +set_prop_cleanup: + nvlist_free(prop_list); + return (ret); +} + +/* + * pcishpc_hp_ops() + * + * Handle hotplug commands + * + * Note: This function is called by DDI HP framework at kernel context only + */ +/* ARGSUSED */ +int +pcishpc_hp_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op, + void *arg, void *result) +{ + pcie_hp_slot_t *slot_p = NULL; + pcie_hp_ctrl_t *ctrl_p; + int ret = DDI_SUCCESS, i; + + PCIE_DBG("pcishpc_hp_ops: dip=%p cn_name=%s op=%x arg=%p\n", + dip, cn_name, op, arg); + + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) + return (DDI_FAILURE); + + for (i = 0; i < PCIE_HP_MAX_SLOTS && ctrl_p->hc_slots[i]; i++) { + if (strcmp(ctrl_p->hc_slots[i]->hs_info.cn_name, cn_name) + == 0) { + /* Match with a physical slot, found */ + slot_p = ctrl_p->hc_slots[i]; + break; + } + } + if (!slot_p) { + PCIE_DBG("pcishpc_hp_ops: Failed to find the slot under" + "dip %p with name: %s; op=%x arg=%p\n", + dip, cn_name, op, arg); + return (DDI_EINVAL); + } + switch (op) { + case DDI_HPOP_CN_GET_STATE: + { + mutex_enter(&ctrl_p->hc_mutex); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + *((ddi_hp_cn_state_t *)result) = slot_p->hs_info.cn_state; + + mutex_exit(&ctrl_p->hc_mutex); + break; + } + case DDI_HPOP_CN_CHANGE_STATE: + { + ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; + + mutex_enter(&slot_p->hs_ctrl->hc_mutex); + + ret = pcishpc_change_slot_state(slot_p, target_state); + *((ddi_hp_cn_state_t *)result) = slot_p->hs_info.cn_state; + + mutex_exit(&slot_p->hs_ctrl->hc_mutex); + break; + } + case DDI_HPOP_CN_PROBE: + ret = pcishpc_slot_probe(slot_p); + + break; + case DDI_HPOP_CN_UNPROBE: + ret = pcishpc_slot_unprobe(slot_p); + + break; + case DDI_HPOP_CN_GET_PROPERTY: + ret = pcishpc_slot_get_property(slot_p, + (ddi_hp_property_t *)arg, (ddi_hp_property_t *)result); + break; + case DDI_HPOP_CN_SET_PROPERTY: + ret = pcishpc_slot_set_property(slot_p, + (ddi_hp_property_t *)arg, (ddi_hp_property_t *)result); + break; + default: + ret = DDI_ENOTSUP; + break; + } + + return (ret); +} + +/* + * Local functions (called within this file) + */ + +/* + * pcishpc_create_controller() + * + * This function allocates and creates an SHPC controller state structure + * and adds it to the linked list of controllers. + */ +static pcie_hp_ctrl_t * +pcishpc_create_controller(dev_info_t *dip) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + pcie_hp_ctrl_t *ctrl_p; + + PCIE_DBG("pcishpc: create controller for %s#%d\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + + ctrl_p = kmem_zalloc(sizeof (pcie_hp_ctrl_t), KM_SLEEP); + ctrl_p->hc_dip = dip; + + cv_init(&ctrl_p->hc_cmd_comp_cv, NULL, CV_DRIVER, NULL); + + /* Init the shpc controller's mutex. */ + mutex_init(&ctrl_p->hc_mutex, NULL, MUTEX_DRIVER, NULL); + + /* HPC initialization is complete now */ + ctrl_p->hc_flags = PCIE_HP_INITIALIZED_FLAG; + bus_p->bus_hp_curr_mode = PCIE_PCI_HP_MODE; + + PCIE_SET_HP_CTRL(dip, ctrl_p); + + PCIE_DBG("pcishpc_create_controller() success\n"); + + return (ctrl_p); +} + + +/* + * pcishpc_setup_controller() + * + * Get the number of HotPlug Slots, and the PCI device information + * for this HotPlug controller. + */ +static int +pcishpc_setup_controller(pcie_hp_ctrl_t *ctrl_p) +{ + uint32_t config; + dev_info_t *ppdip; + + config = pcishpc_read_reg(ctrl_p, PCI_HP_SLOT_CONFIGURATION_REG); + + /* Get the number of HotPlug slots implemented */ + ctrl_p->hc_num_slots_impl = ((config)&31); + + /* + * Initilize the current bus speed and number of hotplug slots + * currently connected. + */ + ctrl_p->hc_curr_bus_speed = -1; + ctrl_p->hc_num_slots_connected = 0; + + /* + * Get the first PCI device Number used. + * + * PCI-X I/O boat workaround. + * The register doesn't set up the correct value. + */ + ppdip = ddi_get_parent(ddi_get_parent(ctrl_p->hc_dip)); + if ((ddi_prop_get_int(DDI_DEV_T_ANY, ppdip, DDI_PROP_DONTPASS, + "vendor-id", -1) == 0x108e) && + (ddi_prop_get_int(DDI_DEV_T_ANY, ppdip, DDI_PROP_DONTPASS, + "device-id", -1) == 0x9010)) + ctrl_p->hc_device_start = 4; + else + ctrl_p->hc_device_start = ((config>>8)&31); + + /* Get the first Physical device number. */ + ctrl_p->hc_phys_start = ((config>>16)&0x7ff); + + /* Check if the device numbers increase or decrease. */ + ctrl_p->hc_device_increases = ((config>>29)&0x1); + + ctrl_p->hc_has_attn = + (config & PCI_HP_SLOT_CONFIG_ATTN_BUTTON) ? B_TRUE : B_FALSE; + ctrl_p->hc_has_mrl = + (config & PCI_HP_SLOT_CONFIG_MRL_SENSOR) ? B_TRUE : B_FALSE; + + ctrl_p->hc_cmd_pending = B_FALSE; + ctrl_p->hc_arbiter_timeout = B_FALSE; + + if (ctrl_p->hc_num_slots_impl > PCIE_HP_MAX_SLOTS) { + PCIE_DBG("pcishpc_setup_controller() too many SHPC " + "slots error\n"); + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + + +/* + * pcishpc_destroy_controller() + * + * This function deallocates all of the SHPC controller resources. + */ +static int +pcishpc_destroy_controller(dev_info_t *dip) +{ + pcie_hp_ctrl_t *ctrl_p; + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + + PCIE_DBG("pcishpc_destroy_controller() called(dip=%p)\n", dip); + + /* get the soft state structure for this dip */ + if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) { + PCIE_DBG("pcishpc_destroy_controller() not found\n"); + return (DDI_FAILURE); + } + + /* + * Deallocate the slot state structures for this controller. + */ + (void) pcishpc_destroy_slots(ctrl_p); + cv_destroy(&ctrl_p->hc_cmd_comp_cv); + mutex_destroy(&ctrl_p->hc_mutex); + kmem_free(ctrl_p, sizeof (pcie_hp_ctrl_t)); + bus_p->bus_hp_curr_mode = PCIE_NONE_HP_MODE; + + PCIE_DBG("pcishpc_destroy_controller() success\n"); + return (DDI_SUCCESS); +} + +/* + * pcishpc_create_slot() + * + * Allocate and add a new HotPlug slot state structure to the linked list. + */ +static pcie_hp_slot_t * +pcishpc_create_slot(pcie_hp_ctrl_t *ctrl_p) +{ + pcie_hp_slot_t *slot_p; + + PCIE_DBG("pcishpc_create_slot() called(ctrl_p=%x)\n", ctrl_p); + + /* Allocate a new slot structure. */ + slot_p = kmem_zalloc(sizeof (pcie_hp_slot_t), KM_SLEEP); + slot_p->hs_ctrl = ctrl_p; + + /* Assign an initial value */ + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_EMPTY; + + PCIE_DBG("pcishpc_create_slot() success\n"); + return (slot_p); +} + +/* + * pcishpc_register_slot() + * + * Create and register a slot with the Solaris HotPlug framework. + */ +static int +pcishpc_register_slot(pcie_hp_ctrl_t *ctrl_p, int slot) +{ + dev_info_t *dip = ctrl_p->hc_dip; + pcie_hp_slot_t *slot_p; + + slot_p = pcishpc_create_slot(ctrl_p); + ctrl_p->hc_slots[slot] = slot_p; + slot_p->hs_num = slot; + + /* Setup the PCI device # for this SHPC slot. */ + if (ctrl_p->hc_device_increases) + slot_p->hs_device_num = ctrl_p->hc_device_start + + slot_p->hs_num; + else + slot_p->hs_device_num = ctrl_p->hc_device_start - + slot_p->hs_num; + + /* Setup the DDI HP framework slot information. */ + slot_p->hs_info.cn_type = DDI_HP_CN_TYPE_PCI; + slot_p->hs_info.cn_type_str = PCIE_PCI_HP_TYPE; + slot_p->hs_info.cn_child = NULL; + + slot_p->hs_minor = PCI_MINOR_NUM( + ddi_get_instance(dip), slot_p->hs_device_num); + slot_p->hs_condition = AP_COND_UNKNOWN; + + /* setup thread for handling ATTN button events */ + if (ctrl_p->hc_has_attn) { + PCIE_DBG("pcishpc_register_slot: " + "setting up ATTN button event " + "handler thread for slot %d\n", slot); + + cv_init(&slot_p->hs_attn_btn_cv, NULL, CV_DRIVER, NULL); + slot_p->hs_attn_btn_pending = B_FALSE; + slot_p->hs_attn_btn_threadp = thread_create(NULL, 0, + pcishpc_attn_btn_handler, + (void *)slot_p, 0, &p0, TS_RUN, minclsyspri); + slot_p->hs_attn_btn_thread_exit = B_FALSE; + } + + /* setup the slot name (used for ap-id) */ + pcishpc_set_slot_name(ctrl_p, slot); + + pcishpc_get_slot_state(slot_p); + if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_ENABLED) + slot_p->hs_condition = AP_COND_OK; + + /* register the slot with DDI HP framework */ + if (ndi_hp_register(dip, &slot_p->hs_info) != NDI_SUCCESS) { + PCIE_DBG("pciehpc_register_slot() failed to register slot %d\n", + slot_p->hs_phy_slot_num); + return (DDI_FAILURE); + } + + pcie_hp_create_occupant_props(dip, makedevice(ddi_driver_major(dip), + slot_p->hs_minor), slot_p->hs_device_num); + + PCIE_DBG("pcishpc_register_slot() success for slot %d\n", slot); + + return (DDI_SUCCESS); +} + +/* + * pcishpc_destroy_slots() + * + * Free up all of the slot resources for this controller. + */ +static int +pcishpc_destroy_slots(pcie_hp_ctrl_t *ctrl_p) +{ + dev_info_t *dip = ctrl_p->hc_dip; + pcie_hp_slot_t *slot_p; + int i; + + PCIE_DBG("pcishpc_destroy_slots() called(ctrl_p=%p)\n", ctrl_p); + + for (i = 0; i < PCIE_HP_MAX_SLOTS; i++) { + if ((slot_p = ctrl_p->hc_slots[i]) == NULL) + continue; + + if (slot_p->hs_attn_btn_threadp != NULL) { + mutex_enter(&ctrl_p->hc_mutex); + slot_p->hs_attn_btn_thread_exit = B_TRUE; + cv_signal(&slot_p->hs_attn_btn_cv); + PCIE_DBG("pcishpc_destroy_slots: " + "waiting for ATTN thread exit\n"); + cv_wait(&slot_p->hs_attn_btn_cv, &ctrl_p->hc_mutex); + PCIE_DBG("pcishpc_destroy_slots: " + "ATTN thread exit\n"); + cv_destroy(&slot_p->hs_attn_btn_cv); + slot_p->hs_attn_btn_threadp = NULL; + mutex_exit(&ctrl_p->hc_mutex); + } + + PCIE_DBG("pcishpc_destroy_slots() (shpc_p=%p)\n" + "destroyed", slot_p); + + pcie_hp_delete_occupant_props(dip, + makedevice(ddi_driver_major(dip), + slot_p->hs_minor)); + + /* unregister the slot with DDI HP framework */ + if (ndi_hp_unregister(dip, slot_p->hs_info.cn_name) != + NDI_SUCCESS) { + PCIE_DBG("pcishpc_destroy_slots() " + "failed to unregister slot %d\n", + slot_p->hs_phy_slot_num); + return (DDI_FAILURE); + } + kmem_free(slot_p->hs_info.cn_name, + strlen(slot_p->hs_info.cn_name) + 1); + kmem_free(slot_p, sizeof (pcie_hp_slot_t)); + } + + return (DDI_SUCCESS); +} + +/* + * pcishpc_enable_irqs() + * + * Enable/unmask the different IRQ's we support from the SHPC controller. + */ +static int +pcishpc_enable_irqs(pcie_hp_ctrl_t *ctrl_p) +{ + uint32_t reg; + int slot; + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG); + + /* Enable all interrupts. */ + reg &= ~PCI_HP_SERR_INT_MASK_ALL; + + pcishpc_write_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG, reg); + + /* Unmask the interrupts for each slot. */ + for (slot = 0; slot < ctrl_p->hc_num_slots_impl; slot++) { + reg = pcishpc_read_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot); + if ((reg & PCI_HP_SLOT_STATE_MASK) == PCI_HP_SLOT_ENABLED) { + reg &= ~(PCI_HP_SLOT_MASK_ALL | + PCI_HP_SLOT_MRL_SERR_MASK); + ctrl_p->hc_num_slots_connected++; + if (ctrl_p->hc_curr_bus_speed == -1) + ctrl_p->hc_curr_bus_speed = + pcishpc_read_reg(ctrl_p, + PCI_HP_PROF_IF_SBCR_REG) & + PCI_HP_SBCR_SPEED_MASK; + } else { + reg &= ~(PCI_HP_SLOT_MASK_ALL); + } + + /* Enable/Unmask all slot interrupts. */ + pcishpc_write_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot, reg); + } + + PCIE_DBG("pcishpc_enable_irqs: ctrl_p 0x%p, " + "current bus speed 0x%x, slots connected 0x%x\n", ctrl_p, + ctrl_p->hc_curr_bus_speed, ctrl_p->hc_num_slots_connected); + + return (DDI_SUCCESS); +} + + +/* + * pcishpc_disable_irqs() + * + * Disable/Mask the different IRQ's we support from the SHPC controller. + */ +static int +pcishpc_disable_irqs(pcie_hp_ctrl_t *ctrl_p) +{ + uint32_t reg; + int slot; + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG); + + /* Mask all interrupts. */ + reg |= PCI_HP_SERR_INT_MASK_ALL; + + pcishpc_write_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG, reg); + + /* Unmask the interrupts for each slot. */ + for (slot = 0; slot < ctrl_p->hc_num_slots_impl; slot++) { + reg = pcishpc_read_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot); + + /* Disable/Mask all slot interrupts. */ + reg |= PCI_HP_SLOT_MASK_ALL; + + pcishpc_write_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot, reg); + } + + PCIE_DBG("pcishpc_disable_irqs: ctrl_p 0x%p, " + "current bus speed 0x%x, slots connected 0x%x\n", ctrl_p, + ctrl_p->hc_curr_bus_speed, ctrl_p->hc_num_slots_connected); + + return (DDI_SUCCESS); +} + +/* + * pcishpc_slot_poweron() + * + * Poweron/Enable the slot. + * + * Note: This function is called by DDI HP framework at kernel context only + */ +/*ARGSUSED*/ +static int +pcishpc_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result_state) +{ + uint32_t status; + + PCIE_DBG("pcishpc_slot_poweron called()\n"); + + ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + /* check if the slot is already in the 'enabled' state */ + if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) { + /* slot is already in the 'enabled' state */ + PCIE_DBG("pcishpc_slot_poweron() slot %d already enabled\n", + slot_p->hs_phy_slot_num); + + *result_state = slot_p->hs_info.cn_state; + return (DDI_SUCCESS); + } + + if (slot_p->hs_info.cn_state == DDI_HP_CN_STATE_EMPTY) { + PCIE_DBG("pcishpc_slot_poweron() slot in empty state\n"); + goto cleanup; + } + + /* make sure the MRL sensor is closed */ + status = pcishpc_read_reg(slot_p->hs_ctrl, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); + + if (status & PCI_HP_SLOT_MRL_STATE_MASK) { + PCIE_DBG("pcishpc_slot_poweron() failed: MRL open\n"); + goto cleanup; + } + + /* Set the Power LED to blink */ + (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_BLINK); + + /* Turn all other LEDS off */ + (void) pcishpc_setled(slot_p, PCIE_HP_FAULT_LED, PCIE_HP_LED_OFF); + (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); + (void) pcishpc_setled(slot_p, PCIE_HP_ACTIVE_LED, PCIE_HP_LED_OFF); + + /* Set the bus speed only if the bus segment is not running */ + if (pcishpc_set_bus_speed(slot_p) != DDI_SUCCESS) { + PCIE_DBG("pcishpc_slot_poweron() setting speed failed\n"); + goto cleanup; + } + + slot_p->hs_ctrl->hc_num_slots_connected++; + + PCIE_DBG("pcishpc_slot_poweron(): slot_p 0x%p, slot state 0x%x, " + "current bus speed 0x%x, slots connected 0x%x\n", slot_p, + slot_p->hs_info.cn_state, slot_p->hs_ctrl->hc_curr_bus_speed, + slot_p->hs_ctrl->hc_num_slots_connected); + + /* Mask or Unmask MRL Sensor SEER bit based on new slot state */ + if (slot_p->hs_ctrl->hc_has_mrl == B_TRUE) { + uint32_t reg; + + reg = pcishpc_read_reg(slot_p->hs_ctrl, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); + + pcishpc_write_reg(slot_p->hs_ctrl, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num, + reg & ~PCI_HP_SLOT_MRL_SERR_MASK); + } + + /* Update the hardware slot state. */ + if (pcishpc_set_slot_state(slot_p, + DDI_HP_CN_STATE_ENABLED) != DDI_SUCCESS) { + PCIE_DBG("pcishpc_slot_poweron() failed\n"); + + pcishpc_get_slot_state(slot_p); + goto cleanup; + } + /* Update the current state. It will be used in pcishpc_setled() */ + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_ENABLED; + + /* Turn the Power LED ON for a enabled slot. */ + (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_ON); + + /* Turn all other LEDS off. */ + (void) pcishpc_setled(slot_p, PCIE_HP_FAULT_LED, PCIE_HP_LED_OFF); + (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); + (void) pcishpc_setled(slot_p, PCIE_HP_ACTIVE_LED, PCIE_HP_LED_OFF); + + /* delay after powerON to let the device initialize itself */ + delay(drv_usectohz(pcishpc_reset_delay)); + + PCIE_DBG("pcishpc_slot_poweron() success!\n"); + + /* + * Want to show up as POWERED state for now. It will be updated to + * ENABLED state when user explicitly enable the slot. + */ + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED; + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + /* + * It should be poweron'ed now. Have a check here in case any + * hardware problems. + */ + if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) { + PCIE_DBG("pcishpc_slot_poweron() failed after hardware" + " registers all programmed.\n"); + + goto cleanup; + } + + *result_state = slot_p->hs_info.cn_state; + + return (DDI_SUCCESS); + +cleanup: + (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_OFF); + return (DDI_FAILURE); +} + +/*ARGSUSED*/ +static int +pcishpc_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result_state) +{ + PCIE_DBG("pcishpc_slot_poweroff called()\n"); + + ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + /* check if the slot is not in the "enabled" or "powered" state */ + if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) { + /* slot is in the 'disabled' state */ + PCIE_DBG("pcishpc_slot_poweroff(): " + "slot %d already disabled\n", slot_p->hs_phy_slot_num); + + *result_state = slot_p->hs_info.cn_state; + return (DDI_SUCCESS); + } + + /* Set the Power LED to blink */ + (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_BLINK); + + /* Turn all other LEDS off */ + (void) pcishpc_setled(slot_p, PCIE_HP_FAULT_LED, PCIE_HP_LED_OFF); + (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); + (void) pcishpc_setled(slot_p, PCIE_HP_ACTIVE_LED, PCIE_HP_LED_OFF); + + if (--slot_p->hs_ctrl->hc_num_slots_connected == 0) + slot_p->hs_ctrl->hc_curr_bus_speed = -1; + + PCIE_DBG("pcishpc_slot_poweroff(): slot_p 0x%p, slot state 0x%x, " + "current bus speed 0x%x, slots connected 0x%x\n", slot_p, + slot_p->hs_info.cn_state, slot_p->hs_ctrl->hc_curr_bus_speed, + slot_p->hs_ctrl->hc_num_slots_connected); + + /* Mask or Unmask MRL Sensor SEER bit based on new slot state */ + if (slot_p->hs_ctrl->hc_has_mrl == B_TRUE) { + uint32_t reg; + + reg = pcishpc_read_reg(slot_p->hs_ctrl, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); + + pcishpc_write_reg(slot_p->hs_ctrl, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num, + reg | PCI_HP_SLOT_MRL_SERR_MASK); + } + + /* Update the hardware slot state. */ + if (pcishpc_set_slot_state(slot_p, DDI_HP_CN_STATE_PRESENT) != + DDI_SUCCESS) { + PCIE_DBG("pcishpc_slot_poweroff() failed\n"); + + pcishpc_get_slot_state(slot_p); + goto cleanup; + } + + /* Update the current state. It will be used in pcishpc_setled() */ + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_PRESENT; + + /* Turn the Power LED OFF for a disabled slot. */ + (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_OFF); + + /* Turn all other LEDS off. */ + (void) pcishpc_setled(slot_p, PCIE_HP_FAULT_LED, PCIE_HP_LED_OFF); + (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); + (void) pcishpc_setled(slot_p, PCIE_HP_ACTIVE_LED, PCIE_HP_LED_OFF); + + /* delay after powerON to let the device initialize itself */ + delay(drv_usectohz(pcishpc_reset_delay)); + + pcishpc_get_slot_state(slot_p); + /* + * It should be poweroff'ed now. Have a check here in case any + * hardware problems. + */ + if (slot_p->hs_info.cn_state > DDI_HP_CN_STATE_PRESENT) { + PCIE_DBG("pcishpc_slot_poweroff() failed after hardware" + " registers all programmed.\n"); + + goto cleanup; + } + + PCIE_DBG("pcishpc_slot_poweroff() success!\n"); + + *result_state = slot_p->hs_info.cn_state; + return (DDI_SUCCESS); + +cleanup: + (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_OFF); + return (DDI_FAILURE); +} + +/* + * pcishpc_slot_probe() + * + * Probe the slot. + * + * Note: This function is called by DDI HP framework at kernel context only + */ +/*ARGSUSED*/ +static int +pcishpc_slot_probe(pcie_hp_slot_t *slot_p) +{ + mutex_enter(&slot_p->hs_ctrl->hc_mutex); + + PCIE_DBG("pcishpc_slot_probe called()\n"); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + /* + * Probe a given PCI Hotplug Connection (CN). + */ + if (pcie_hp_probe(slot_p) != DDI_SUCCESS) { + (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, + PCIE_HP_LED_BLINK); + + PCIE_DBG("pcishpc_slot_probe() failed\n"); + + mutex_exit(&slot_p->hs_ctrl->hc_mutex); + return (DDI_FAILURE); + } + + PCIE_DBG("pcishpc_slot_probe() success!\n"); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + mutex_exit(&slot_p->hs_ctrl->hc_mutex); + return (DDI_SUCCESS); +} + +/* + * pcishpc_slot_unprobe() + * + * Unprobe the slot. + * + * Note: This function is called by DDI HP framework at kernel context only + */ +/*ARGSUSED*/ +static int +pcishpc_slot_unprobe(pcie_hp_slot_t *slot_p) +{ + mutex_enter(&slot_p->hs_ctrl->hc_mutex); + + PCIE_DBG("pcishpc_slot_unprobe called()\n"); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + /* + * Unprobe a given PCI Hotplug Connection (CN). + */ + if (pcie_hp_unprobe(slot_p) != DDI_SUCCESS) { + (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, + PCIE_HP_LED_BLINK); + + PCIE_DBG("pcishpc_slot_unprobe() failed\n"); + + mutex_exit(&slot_p->hs_ctrl->hc_mutex); + return (DDI_FAILURE); + } + + PCIE_DBG("pcishpc_slot_unprobe() success!\n"); + + /* get the current slot state */ + pcishpc_get_slot_state(slot_p); + + mutex_exit(&slot_p->hs_ctrl->hc_mutex); + return (DDI_SUCCESS); +} + +static int +pcishpc_upgrade_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state; + int rv = DDI_SUCCESS; + + if (target_state > DDI_HP_CN_STATE_ENABLED) { + return (DDI_EINVAL); + } + + curr_state = slot_p->hs_info.cn_state; + while ((curr_state < target_state) && (rv == DDI_SUCCESS)) { + + switch (curr_state) { + case DDI_HP_CN_STATE_EMPTY: + /* + * From EMPTY to PRESENT, just check the hardware + * slot state. + */ + pcishpc_get_slot_state(slot_p); + curr_state = slot_p->hs_info.cn_state; + if (curr_state < DDI_HP_CN_STATE_PRESENT) + rv = DDI_FAILURE; + break; + case DDI_HP_CN_STATE_PRESENT: + rv = pcishpc_slot_poweron(slot_p, &curr_state); + break; + case DDI_HP_CN_STATE_POWERED: + curr_state = slot_p->hs_info.cn_state = + DDI_HP_CN_STATE_ENABLED; + break; + default: + /* should never reach here */ + ASSERT("unknown devinfo state"); + } + } + + return (rv); +} + +static int +pcishpc_downgrade_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state; + int rv = DDI_SUCCESS; + + + curr_state = slot_p->hs_info.cn_state; + while ((curr_state > target_state) && (rv == DDI_SUCCESS)) { + + switch (curr_state) { + case DDI_HP_CN_STATE_PRESENT: + /* + * From PRESENT to EMPTY, just check hardware + * slot state. + */ + pcishpc_get_slot_state(slot_p); + curr_state = slot_p->hs_info.cn_state; + if (curr_state >= DDI_HP_CN_STATE_PRESENT) + rv = DDI_FAILURE; + break; + case DDI_HP_CN_STATE_POWERED: + rv = pcishpc_slot_poweroff(slot_p, &curr_state); + + break; + case DDI_HP_CN_STATE_ENABLED: + curr_state = slot_p->hs_info.cn_state = + DDI_HP_CN_STATE_POWERED; + + break; + default: + /* should never reach here */ + ASSERT("unknown devinfo state"); + } + } + + return (rv); +} + +/* Change slot state to a target state */ +static int +pcishpc_change_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state; + int rv; + + pcishpc_get_slot_state(slot_p); + curr_state = slot_p->hs_info.cn_state; + + if (curr_state == target_state) { + return (DDI_SUCCESS); + } + if (curr_state < target_state) { + + rv = pcishpc_upgrade_slot_state(slot_p, target_state); + } else { + rv = pcishpc_downgrade_slot_state(slot_p, target_state); + } + + return (rv); +} + +/* + * pcishpc_issue_command() + * + * Sends a command to the SHPC controller. + */ +static int +pcishpc_issue_command(pcie_hp_ctrl_t *ctrl_p, uint32_t cmd_code) +{ + int retCode; + + ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex)); + + PCIE_DBG("pcishpc_issue_command() cmd_code=%02x\n", cmd_code); + + ctrl_p->hc_cmd_pending = B_TRUE; + + /* Write the command to the SHPC controller. */ + pcishpc_write_reg(ctrl_p, PCI_HP_COMMAND_STATUS_REG, cmd_code); + + while (ctrl_p->hc_cmd_pending == B_TRUE) + cv_wait(&ctrl_p->hc_cmd_comp_cv, &ctrl_p->hc_mutex); + + /* Wait until the SHPC controller processes the command. */ + retCode = pcishpc_wait_busy(ctrl_p); + + /* Make sure the command completed. */ + if (retCode == DDI_SUCCESS) { + /* Did the command fail to generate the command complete IRQ? */ + if (ctrl_p->hc_cmd_pending != B_FALSE) { + PCIE_DBG("pcishpc_issue_command() Failed on " + "generate cmd complete IRQ\n"); + retCode = DDI_FAILURE; + } + } + + if (retCode == DDI_FAILURE) + PCIE_DBG("pcishpc_issue_command() Failed on cmd_code=%02x\n", + cmd_code); + else + PCIE_DBG("pcishpc_issue_command() Success on " + "cmd_code=%02x\n", cmd_code); + + return (retCode); +} + +/* + * pcishpc_wait_busy() + * + * Wait until the SHPC controller is not busy. + */ +static int +pcishpc_wait_busy(pcie_hp_ctrl_t *ctrl_p) +{ + uint32_t status; + + /* Wait until SHPC controller is NOT busy */ + for (;;) { + status = pcishpc_read_reg(ctrl_p, PCI_HP_COMMAND_STATUS_REG); + + /* Is there an MRL Sensor error? */ + if ((status & PCI_HP_COMM_STS_ERR_MASK) == + PCI_HP_COMM_STS_ERR_MRL_OPEN) { + PCIE_DBG("pcishpc_wait_busy() ERROR: " + "MRL Sensor error\n"); + break; + } + + /* Is there an Invalid command error? */ + if ((status & PCI_HP_COMM_STS_ERR_MASK) == + PCI_HP_COMM_STS_ERR_INVALID_COMMAND) { + PCIE_DBG("pcishpc_wait_busy() ERROR: Invalid " + "command error\n"); + break; + } + + /* Is there an Invalid Speed/Mode error? */ + if ((status & PCI_HP_COMM_STS_ERR_MASK) == + PCI_HP_COMM_STS_ERR_INVALID_SPEED) { + PCIE_DBG("pcishpc_wait_busy() ERROR: Invalid " + "Speed/Mode error\n"); + break; + } + + /* Is the SHPC controller not BUSY? */ + if (!(status & PCI_HP_COMM_STS_CTRL_BUSY)) { + /* Return Success. */ + return (DDI_SUCCESS); + } + + PCIE_DBG("pcishpc_wait_busy() SHPC controller busy. Waiting\n"); + + /* Wait before polling the status register again. */ + delay(drv_usectohz(PCIE_HP_CMD_WAIT_TIME)); + } + + return (DDI_FAILURE); +} + +static void +pcishpc_attn_btn_handler(pcie_hp_slot_t *slot_p) +{ + pcie_hp_led_state_t hs_power_led_state; + callb_cpr_t cprinfo; + + PCIE_DBG("pcishpc_attn_btn_handler: thread started\n"); + + CALLB_CPR_INIT(&cprinfo, &slot_p->hs_ctrl->hc_mutex, + callb_generic_cpr, "pcishpc_attn_btn_handler"); + + mutex_enter(&slot_p->hs_ctrl->hc_mutex); + + /* wait for ATTN button event */ + cv_wait(&slot_p->hs_attn_btn_cv, &slot_p->hs_ctrl->hc_mutex); + + while (slot_p->hs_attn_btn_thread_exit == B_FALSE) { + if (slot_p->hs_attn_btn_pending == B_TRUE) { + /* get the current state of power LED */ + hs_power_led_state = slot_p->hs_power_led_state; + + /* Blink the Power LED while we wait for 5 seconds */ + (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, + PCIE_HP_LED_BLINK); + + /* wait for 5 seconds before taking any action */ + if (cv_timedwait(&slot_p->hs_attn_btn_cv, + &slot_p->hs_ctrl->hc_mutex, + ddi_get_lbolt() + SEC_TO_TICK(5)) == -1) { + /* + * It is a time out; + * make sure the ATTN pending flag is + * still ON before sending the event + * to DDI HP framework. + */ + if (slot_p->hs_attn_btn_pending == B_TRUE) { + int hint; + + /* restore the power LED state */ + (void) pcishpc_setled(slot_p, + PCIE_HP_POWER_LED, + hs_power_led_state); + /* + * send the ATTN button event + * to DDI HP framework + */ + slot_p->hs_attn_btn_pending = B_FALSE; + + pcishpc_get_slot_state(slot_p); + + if (slot_p->hs_info.cn_state <= + DDI_HP_CN_STATE_PRESENT) { + /* + * Insertion. + */ + hint = SE_INCOMING_RES; + } else { + /* + * Want to remove; + */ + hint = SE_OUTGOING_RES; + } + pcie_hp_gen_sysevent_req( + slot_p->hs_info.cn_name, + hint, + slot_p->hs_ctrl->hc_dip, + KM_SLEEP); + + continue; + } + } + + /* restore the power LED state */ + (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, + hs_power_led_state); + continue; + } + + /* wait for another ATTN button event */ + cv_wait(&slot_p->hs_attn_btn_cv, &slot_p->hs_ctrl->hc_mutex); + } + + PCIE_DBG("pcishpc_attn_btn_handler: thread exit\n"); + cv_signal(&slot_p->hs_attn_btn_cv); + CALLB_CPR_EXIT(&cprinfo); + thread_exit(); +} + +/* + * pcishpc_get_slot_state() + * + * Get the state of the slot. + * The slot state should have been initialized before this function gets called. + */ +static void +pcishpc_get_slot_state(pcie_hp_slot_t *slot_p) +{ + uint32_t reg; + ddi_hp_cn_state_t curr_state = slot_p->hs_info.cn_state; + + /* Read the logical slot register for this Slot. */ + reg = pcishpc_read_reg(slot_p->hs_ctrl, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); + + /* Convert from the SHPC slot state to the HPC slot state. */ + slot_p->hs_info.cn_state = pcishpc_slot_shpc_to_hpc(reg); + if (curr_state == DDI_HP_CN_STATE_POWERED && + slot_p->hs_info.cn_state > DDI_HP_CN_STATE_POWERED) { + /* + * Keep POWERED state if it is currently POWERED state because + * this driver does not really implement enable/disable + * slot operations. That is, when poweron, it actually enables + * the slot also. + * So, from hardware view, POWERED == ENABLED. + * But, when user explicitly change to POWERED state, it should + * be kept until user explicitly change to other states later. + */ + slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED; + } + + /* Convert from the SHPC Power LED state to the HPC Power LED state. */ + slot_p->hs_power_led_state = pcishpc_led_shpc_to_hpc((reg>>2)&3); + + /* Convert from the SHPC Attn LED state to the HPC Attn LED state. */ + slot_p->hs_attn_led_state = pcishpc_led_shpc_to_hpc((reg>>4)&3); + + /* We don't have a fault LED so just default it to OFF. */ + slot_p->hs_fault_led_state = PCIE_HP_LED_OFF; + + /* We don't have an active LED so just default it to OFF. */ + slot_p->hs_active_led_state = PCIE_HP_LED_OFF; +} + +/* + * pcishpc_set_slot_state() + * + * Updates the slot's state and leds. + */ +static int +pcishpc_set_slot_state(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t new_slot_state) +{ + uint32_t reg, cmd_code; + ddi_hp_cn_state_t curr_state; + + ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); + + reg = pcishpc_read_reg(slot_p->hs_ctrl, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); + + /* Default all states to unchanged. */ + cmd_code = ((1 + slot_p->hs_num) << 8); + + /* Has the slot state changed? */ + curr_state = pcishpc_slot_shpc_to_hpc(reg); + if (curr_state != new_slot_state) { + PCIE_DBG("pcishpc_set_slot_state() Slot State changed"); + + /* Set the new slot state in the Slot operation command. */ + cmd_code |= pcishpc_slot_hpc_to_shpc(new_slot_state); + } + + /* Has the Power LED state changed? */ + if (slot_p->hs_power_led_state != pcishpc_led_shpc_to_hpc((reg>>2)&3)) { + PCIE_DBG("pcishpc_set_slot_state() Power LED State changed\n"); + + /* Set the new power led state in the Slot operation command. */ + cmd_code |= + (pcishpc_led_hpc_to_shpc(slot_p->hs_power_led_state) << 2); + } + + /* Has the Attn LED state changed? */ + if (slot_p->hs_attn_led_state != pcishpc_led_shpc_to_hpc((reg>>4)&3)) { + PCIE_DBG("pcishpc_set_slot_state() Attn LED State changed\n"); + + /* Set the new attn led state in the Slot operation command. */ + cmd_code |= + (pcishpc_led_hpc_to_shpc(slot_p->hs_attn_led_state) << 4); + } + + return (pcishpc_issue_command(slot_p->hs_ctrl, cmd_code)); +} + +/* + * setup slot name/slot-number info. + */ +static void +pcishpc_set_slot_name(pcie_hp_ctrl_t *ctrl_p, int slot) +{ + pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[slot]; + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + uchar_t *slotname_data; + int *slotnum; + uint_t count; + int len; + uchar_t *s; + uint32_t bit_mask; + int pci_id_cnt, pci_id_bit; + int slots_before, found; + int invalid_slotnum = 0; + + if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ctrl_p->hc_dip, + DDI_PROP_DONTPASS, "physical-slot#", &slotnum, &count) == + DDI_PROP_SUCCESS) { + slot_p->hs_phy_slot_num = slotnum[0]; + ddi_prop_free(slotnum); + } else { + if (ctrl_p->hc_device_increases) + slot_p->hs_phy_slot_num = ctrl_p->hc_phys_start + slot; + else + slot_p->hs_phy_slot_num = ctrl_p->hc_phys_start - slot; + + if ((ndi_prop_update_int(DDI_DEV_T_NONE, ctrl_p->hc_dip, + "physical-slot#", slot_p->hs_phy_slot_num)) != DDI_SUCCESS) + PCIE_DBG("pcishpc_set_slot_name(): failed to " + "create phyical-slot#%d\n", + slot_p->hs_phy_slot_num); + } + + /* Platform may not have initialized it */ + if (!slot_p->hs_phy_slot_num) { + slot_p->hs_phy_slot_num = pci_config_get8(bus_p->bus_cfg_hdl, + PCI_BCNF_SECBUS); + invalid_slotnum = 1; + } + slot_p->hs_info.cn_num = slot_p->hs_phy_slot_num; + slot_p->hs_info.cn_num_dpd_on = DDI_HP_CN_NUM_NONE; + + /* + * construct the slot_name: + * if "slot-names" property exists then use that name + * else if valid slot number exists then it is "pci<slot-num>". + * else it will be "pci<sec-bus-number>dev<dev-number>" + */ + if (ddi_getlongprop(DDI_DEV_T_ANY, ctrl_p->hc_dip, DDI_PROP_DONTPASS, + "slot-names", (caddr_t)&slotname_data, &len) == DDI_PROP_SUCCESS) { + bit_mask = slotname_data[3] | (slotname_data[2] << 8) | + (slotname_data[1] << 16) | (slotname_data[0] << 24); + + pci_id_bit = 1; + pci_id_cnt = slots_before = found = 0; + + /* + * Walk the bit mask until we find the bit that corresponds + * to our slots device number. We count how many bits + * we find before we find our slot's bit. + */ + while (!found && (pci_id_cnt < 32)) { + while (slot_p->hs_device_num != pci_id_cnt) { + + /* + * Find the next bit set. + */ + while (!(bit_mask & pci_id_bit) && + (pci_id_cnt < 32)) { + pci_id_bit = pci_id_bit << 1; + pci_id_cnt++; + } + + if (slot_p->hs_device_num != pci_id_cnt) + slots_before++; + else + found = 1; + } + } + + if (pci_id_cnt < 32) { + + /* + * Set ptr to first string. + */ + s = slotname_data + 4; + + /* + * Increment past all the strings for the slots + * before ours. + */ + while (slots_before) { + while (*s != NULL) + s++; + s++; + slots_before--; + } + + slot_p->hs_info.cn_name = i_ddi_strdup((char *)s, + KM_SLEEP); + kmem_free(slotname_data, len); + return; + } + + /* slot-names entry not found */ + PCIE_DBG("pcishpc_set_slot_name(): " + "No slot-names entry found for slot #%d\n", + slot_p->hs_phy_slot_num); + kmem_free(slotname_data, len); + } + + if (invalid_slotnum) { + char tmp_name[256]; + + (void) snprintf(tmp_name, sizeof (tmp_name), "pci%d", + slot_p->hs_device_num); + slot_p->hs_info.cn_name = i_ddi_strdup(tmp_name, KM_SLEEP); + } else { + char tmp_name[256]; + + (void) snprintf(tmp_name, sizeof (tmp_name), "pci%d", + slot_p->hs_phy_slot_num); + slot_p->hs_info.cn_name = i_ddi_strdup(tmp_name, KM_SLEEP); + } +} + +/* + * pcishpc_set_bus_speed() + * + * Set the bus speed and mode. + */ +static int +pcishpc_set_bus_speed(pcie_hp_slot_t *slot_p) +{ + pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; + int curr_speed = ctrl_p->hc_curr_bus_speed; + int speed = -1; + int avail_slots; + uint32_t status, slots_avail1_reg, slots_avail2_reg; + + ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); + + /* Make sure that the slot is in a correct state */ + status = pcishpc_read_reg(ctrl_p, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); + + /* Return failure if the slot is empty */ + if ((status & PCI_HP_SLOT_CARD_EMPTY_MASK) == + PCI_HP_SLOT_CARD_EMPTY_MASK) { + PCIE_DBG("pcishpc_set_bus_speed() failed: " + "the slot is empty\n"); + return (DDI_FAILURE); + } + + /* Return failure if the slot is not in disabled state */ + if ((status & PCI_HP_SLOT_STATE_MASK) != PCI_HP_SLOT_DISABLED) { + PCIE_DBG("pcishpc_set_bus_speed() failed: " + "incorrect slot state\n"); + return (DDI_FAILURE); + } + + /* Set the "power-only" mode for the slot */ + if (pcishpc_issue_command(ctrl_p, ((1+slot_p->hs_num)<<8) | + PCI_HP_SLOT_POWER_ONLY) != DDI_SUCCESS) { + PCIE_DBG("pcishpc_set_bus_speed() failed to set " + "the slot %d in the power-only mode\n", slot_p->hs_num); + return (DDI_FAILURE); + } + + /* Wait for power good */ + delay(drv_usectohz(PCIE_HP_POWER_GOOD_WAIT_TIME)); + + /* Make sure that the slot is in "power-only" state */ + status = pcishpc_read_reg(ctrl_p, + PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); + + if ((status & PCI_HP_SLOT_STATE_MASK) != PCI_HP_SLOT_POWER_ONLY) { + PCIE_DBG("pcishpc_set_bus_speed() " + "power-only failed: incorrect slot state\n"); + return (DDI_FAILURE); + } + + slots_avail1_reg = pcishpc_read_reg(ctrl_p, + PCI_HP_SLOTS_AVAIL_I_REG); + slots_avail2_reg = pcishpc_read_reg(ctrl_p, + PCI_HP_SLOTS_AVAIL_II_REG); + + /* + * Check if SHPC has available slots and select the highest + * available bus speed for the slot. + * + * The bus speed codes are: + * 100 - 133Mhz; <--+ + * 011 - 100Mhz; <--+ PCI-X + * 010 - 66Mhz; <--+ + * + * 001 - 66Mhz; <--+ + * 000 - 33Mhz <--+ Conv PCI + */ + switch (status & PCI_HP_SLOT_PCIX_CAPABLE_MASK) { + case PCI_HP_SLOT_133MHZ_PCIX_CAPABLE: + avail_slots = (slots_avail1_reg >> + PCI_HP_AVAIL_133MHZ_PCIX_SPEED_SHIFT) & + PCI_HP_AVAIL_SPEED_MASK; + + if (((curr_speed == -1) && avail_slots) || + (curr_speed == PCI_HP_SBCR_133MHZ_PCIX_SPEED)) { + speed = PCI_HP_SBCR_133MHZ_PCIX_SPEED; + break; + } + /* FALLTHROUGH */ + case PCI_HP_SLOT_100MHZ_PCIX_CAPABLE: + avail_slots = (slots_avail1_reg >> + PCI_HP_AVAIL_100MHZ_PCIX_SPEED_SHIFT) & + PCI_HP_AVAIL_SPEED_MASK; + + if (((curr_speed == -1) && avail_slots) || + (curr_speed == PCI_HP_SBCR_100MHZ_PCIX_SPEED)) { + speed = PCI_HP_SBCR_100MHZ_PCIX_SPEED; + break; + } + /* FALLTHROUGH */ + case PCI_HP_SLOT_66MHZ_PCIX_CAPABLE: + avail_slots = (slots_avail1_reg >> + PCI_HP_AVAIL_66MHZ_PCIX_SPEED_SHIFT) & + PCI_HP_AVAIL_SPEED_MASK; + + if (((curr_speed == -1) && avail_slots) || + (curr_speed == PCI_HP_SBCR_66MHZ_PCIX_SPEED)) { + speed = PCI_HP_SBCR_66MHZ_PCIX_SPEED; + break; + } + /* FALLTHROUGH */ + default: + avail_slots = (slots_avail2_reg >> + PCI_HP_AVAIL_66MHZ_CONV_SPEED_SHIFT) & + PCI_HP_AVAIL_SPEED_MASK; + + if ((status & PCI_HP_SLOT_66MHZ_CONV_CAPABLE) && + (((curr_speed == -1) && avail_slots) || + (curr_speed == PCI_HP_SBCR_66MHZ_CONV_SPEED))) { + speed = PCI_HP_SBCR_66MHZ_CONV_SPEED; + } else { + avail_slots = (slots_avail1_reg >> + PCI_HP_AVAIL_33MHZ_CONV_SPEED_SHIFT) & + PCI_HP_AVAIL_SPEED_MASK; + + if (((curr_speed == -1) && (avail_slots)) || + (curr_speed == PCI_HP_SBCR_33MHZ_CONV_SPEED)) { + speed = PCI_HP_SBCR_33MHZ_CONV_SPEED; + } else { + PCIE_DBG("pcishpc_set_bus_speed() " + " failed to set the bus speed, slot# %d\n", + slot_p->hs_num); + return (DDI_FAILURE); + } + } + break; + } + + /* + * If the bus segment is already running, check to see the card + * in the slot can support the current bus speed. + */ + if (curr_speed == speed) { + /* + * Check to see there is any slot available for the current + * bus speed. Otherwise, we need fail the current slot connect + * request. + */ + return ((avail_slots <= ctrl_p->hc_num_slots_connected) ? + DDI_FAILURE : DDI_SUCCESS); + } + + /* Set the bus speed */ + if (pcishpc_issue_command(ctrl_p, PCI_HP_COMM_STS_SET_SPEED | + speed) == DDI_FAILURE) { + PCIE_DBG("pcishpc_set_bus_speed() failed " + "to set bus %d speed\n", slot_p->hs_num); + return (DDI_FAILURE); + } + + /* Check the current bus speed */ + status = pcishpc_read_reg(ctrl_p, PCI_HP_PROF_IF_SBCR_REG) & + PCI_HP_SBCR_SPEED_MASK; + if ((status & PCI_HP_SBCR_SPEED_MASK) != speed) { + PCIE_DBG("pcishpc_set_bus_speed() an incorrect " + "bus speed, slot = 0x%x, speed = 0x%x\n", + slot_p->hs_num, status & PCI_HP_SBCR_SPEED_MASK); + return (DDI_FAILURE); + } + + + /* Save the current bus speed */ + ctrl_p->hc_curr_bus_speed = speed; + + return (DDI_SUCCESS); +} + +/* + * pcishpc_setled() + * + * Change the state of a slot's LED. + */ +static int +pcishpc_setled(pcie_hp_slot_t *slot_p, pcie_hp_led_t led, + pcie_hp_led_state_t state) +{ + ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); + + switch (led) { + case PCIE_HP_FAULT_LED: + PCIE_DBG("pcishpc_setled() - PCIE_HP_FAULT_LED " + "(set %s)\n", pcishpc_slot_textledstate(state)); + slot_p->hs_fault_led_state = state; + break; + + case PCIE_HP_POWER_LED: + PCIE_DBG("pcishpc_setled() - PCIE_HP_POWER_LED " + "(set %s)\n", pcishpc_slot_textledstate(state)); + slot_p->hs_power_led_state = state; + break; + + case PCIE_HP_ATTN_LED: + PCIE_DBG("pcishpc_setled() - PCIE_HP_ATTN_LED " + "(set %s)\n", pcishpc_slot_textledstate(state)); + slot_p->hs_attn_led_state = state; + break; + + case PCIE_HP_ACTIVE_LED: + PCIE_DBG("pcishpc_setled() - PCIE_HP_ACTIVE_LED " + "(set %s)\n", pcishpc_slot_textledstate(state)); + slot_p->hs_active_led_state = state; + break; + } + + return (pcishpc_set_slot_state(slot_p, slot_p->hs_info.cn_state)); +} + +/* + * pcishpc_led_shpc_to_hpc() + * + * Convert from SHPC indicator status to HPC indicator status. + */ +static int +pcishpc_led_shpc_to_hpc(int state) +{ + switch (state) { + case 1: /* SHPC On bits b01 */ + return (PCIE_HP_LED_ON); + case 2: /* SHPC Blink bits b10 */ + return (PCIE_HP_LED_BLINK); + case 3: /* SHPC Off bits b11 */ + return (PCIE_HP_LED_OFF); + } + + return (PCIE_HP_LED_OFF); +} + + +/* + * pcishpc_led_hpc_to_shpc() + * + * Convert from HPC indicator status to SHPC indicator status. + */ +static int +pcishpc_led_hpc_to_shpc(int state) +{ + switch (state) { + case PCIE_HP_LED_ON: + return (1); /* SHPC On bits b01 */ + case PCIE_HP_LED_BLINK: + return (2); /* SHPC Blink bits b10 */ + case PCIE_HP_LED_OFF: + return (3); /* SHPC Off bits b11 */ + } + + return (3); /* SHPC Off bits b11 */ +} + +/* + * pcishpc_slot_shpc_to_hpc() + * + * Convert from SHPC slot state to HPC slot state. + * The argument shpc_state is expected to be read from the slot register. + */ +static int +pcishpc_slot_shpc_to_hpc(int shpc_state) +{ + if ((shpc_state & PCI_HP_SLOT_CARD_EMPTY_MASK) == + PCI_HP_SLOT_CARD_EMPTY_MASK) + return (DDI_HP_CN_STATE_EMPTY); + + switch (shpc_state & PCI_HP_SLOT_STATE_MASK) { + case PCI_HP_SLOT_POWER_ONLY: /* SHPC Powered Only */ + return (DDI_HP_CN_STATE_POWERED); + + case PCI_HP_SLOT_ENABLED: /* SHPC Enabled */ + return (DDI_HP_CN_STATE_ENABLED); + + case PCI_HP_SLOT_DISABLED: /* SHPC Disabled */ + default : /* SHPC Reserved */ + return (DDI_HP_CN_STATE_PRESENT); + } +} + +/* + * pcishpc_slot_hpc_to_shpc() + * + * Convert from HPC slot state to SHPC slot state. + */ +static int +pcishpc_slot_hpc_to_shpc(int state) +{ + switch (state) { + case DDI_HP_CN_STATE_EMPTY: + return (0); + + case DDI_HP_CN_STATE_POWERED: + return (PCI_HP_SLOT_POWER_ONLY); + + case DDI_HP_CN_STATE_ENABLED: + return (PCI_HP_SLOT_ENABLED); + + default: + return (PCI_HP_SLOT_DISABLED); + } +} + +/* + * pcishpc_slot_textslotstate() + * + * Convert the request into a text message. + */ +static char * +pcishpc_slot_textslotstate(ddi_hp_cn_state_t state) +{ + /* Convert an HPC slot state into a textual string. */ + if (state == DDI_HP_CN_STATE_EMPTY) + return ("HPC_SLOT_EMPTY"); + else if (state == DDI_HP_CN_STATE_ENABLED) + return ("HPC_SLOT_ENABLED"); + else if (state == DDI_HP_CN_STATE_POWERED) + return ("HPC_SLOT_POWERED_ONLY"); + else + return ("HPC_SLOT_DISABLED"); +} + + +/* + * pcishpc_slot_textledstate() + * + * Convert the led state into a text message. + */ +static char * +pcishpc_slot_textledstate(pcie_hp_led_state_t state) +{ + /* Convert an HPC led state into a textual string. */ + switch (state) { + case PCIE_HP_LED_OFF: + return ("off"); + + case PCIE_HP_LED_ON: + return ("on"); + + case PCIE_HP_LED_BLINK: + return ("blink"); + } + return ("unknown"); +} + + +/* + * pcishpc_read_reg() + * + * Read from a SHPC controller register. + */ +static uint32_t +pcishpc_read_reg(pcie_hp_ctrl_t *ctrl_p, int reg) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + /* Setup the SHPC dword select register. */ + pci_config_put8(bus_p->bus_cfg_hdl, + bus_p->bus_pci_hp_off + PCI_HP_DWORD_SELECT_OFF, (uint8_t)reg); + + /* Read back the SHPC dword select register and verify. */ + if (pci_config_get8(bus_p->bus_cfg_hdl, bus_p->bus_pci_hp_off + + PCI_HP_DWORD_SELECT_OFF) != (uint8_t)reg) { + PCIE_DBG("pcishpc_read_reg() - Failed writing DWORD " + "select reg\n"); + return (0xFFFFFFFF); + } + + /* Read from the SHPC dword data register. */ + return (pci_config_get32(bus_p->bus_cfg_hdl, + bus_p->bus_pci_hp_off + PCI_HP_DWORD_DATA_OFF)); +} + + +/* + * pcishpc_write_reg() + * + * Write to a SHPC controller register. + */ +static void +pcishpc_write_reg(pcie_hp_ctrl_t *ctrl_p, int reg, uint32_t data) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); + + /* Setup the SHPC dword select register. */ + pci_config_put8(bus_p->bus_cfg_hdl, + bus_p->bus_pci_hp_off + PCI_HP_DWORD_SELECT_OFF, (uint8_t)reg); + + /* Read back the SHPC dword select register and verify. */ + if (pci_config_get8(bus_p->bus_cfg_hdl, bus_p->bus_pci_hp_off + + PCI_HP_DWORD_SELECT_OFF) != (uint8_t)reg) { + PCIE_DBG("pcishpc_write_reg() - Failed writing " + "DWORD select reg\n"); + return; + } + + /* Write to the SHPC dword data register. */ + pci_config_put32(bus_p->bus_cfg_hdl, + bus_p->bus_pci_hp_off + PCI_HP_DWORD_DATA_OFF, data); + + /* + * Issue a read of the VendorID/DeviceID just to force the previous + * write to complete. This is probably not necessary, but it does + * help enforce ordering if there is an issue. + */ + (void) pci_config_get16(bus_p->bus_cfg_hdl, PCI_CONF_VENID); +} + + +#ifdef DEBUG +/* + * pcishpc_dump_regs() + * + * Dumps all of the SHPC controller registers. + */ +static void +pcishpc_dump_regs(pcie_hp_ctrl_t *ctrl_p) +{ + int slot, numSlots; + uint32_t reg; + char *state; + + if (!pcie_debug_flags) + return; + + PCIE_DBG("pcishpc_dump_regs() called:\n"); + PCIE_DBG("=========================================================="); + + PCIE_DBG("SHPC Base Offset " + ": 0x%08x\n", pcishpc_read_reg(ctrl_p, PCI_HP_BASE_OFFSET_REG)); + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_SLOTS_AVAIL_I_REG); + + PCIE_DBG("Number of PCIX slots avail (33 Mhz) : %d\n", + (reg & 31)); + + PCIE_DBG("Number of PCIX slots avail (66 Mhz) : %d\n", + ((reg>>8) & 31)); + + PCIE_DBG("Number of PCIX slots avail (100 Mhz) : %d\n", + ((reg>>16) & 31)); + + PCIE_DBG("Number of PCIX slots avail (133 Mhz) : %d\n", + ((reg>>24) & 31)); + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_SLOTS_AVAIL_II_REG); + + PCIE_DBG("Number of conventional PCI slots (66 Mhz) : %d\n", + (reg & 31)); + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_SLOT_CONFIGURATION_REG); + + numSlots = (reg & 31); + + PCIE_DBG("Number of Slots connected to this port : %d\n", + numSlots); + + PCIE_DBG("PCI Device # for First HotPlug Slot : %d\n", + ((reg>>8) & 31)); + + PCIE_DBG("Physical Slot # for First PCI Device # : %d\n", + ((reg>>16) & 0x7ff)); + + PCIE_DBG("Physical Slot Number Up/Down : %d\n", + ((reg>>29) & 0x1)); + + PCIE_DBG("MRL Sensor Implemented : %s\n", + (reg & PCI_HP_SLOT_CONFIG_MRL_SENSOR) ? "Yes" : "No"); + + PCIE_DBG("Attention Button Implemented : %s\n", + (reg & PCI_HP_SLOT_CONFIG_ATTN_BUTTON) ? "Yes" : "No"); + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_PROF_IF_SBCR_REG); + + switch (reg & 7) { + case 0: + state = "33Mhz Conventional PCI"; + break; + case 1: + state = "66Mhz Conventional PCI"; + break; + case 2: + state = "66Mhz PCI-X"; + break; + case 3: + state = "100Mhz PCI-X"; + break; + case 4: + state = "133Mhz PCI-X"; + break; + default: + state = "Reserved (Error)"; + break; + } + + PCIE_DBG("Current Port Operation Mode : %s\n", state); + + PCIE_DBG("SHPC Interrupt Message Number : %d\n", + ((reg>>16) &31)); + + PCIE_DBG("SHPC Programming Interface : %d\n", + ((reg>>24) & 0xff)); + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_COMMAND_STATUS_REG); + + PCIE_DBG("SHPC Command Code : %d\n", + (reg & 0xff)); + + PCIE_DBG("SHPC Target Slot : %d\n", + ((reg>>8) & 31)); + + PCIE_DBG("SHPC Controller Busy : %s\n", + ((reg>>16) & 1) ? "Yes" : "No"); + + PCIE_DBG("SHPC Controller Err: MRL Sensor : %s\n", + ((reg>>17) & 1) ? "Yes" : "No"); + + PCIE_DBG("SHPC Controller Err: Invalid Command : %s\n", + ((reg>>18) & 1) ? "Yes" : "No"); + + PCIE_DBG("SHPC Controller Err: Invalid Speed/Mode : %s\n", + ((reg>>19) & 1) ? "Yes" : "No"); + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_IRQ_LOCATOR_REG); + + PCIE_DBG("Command Completion Interrupt Pending : %s\n", + (reg & PCI_HP_IRQ_CMD_COMPLETE) ? "Yes" : "No"); + + for (slot = 0; slot < numSlots; slot++) { + PCIE_DBG("Slot %d Interrupt Pending : %s\n", slot+1, + (reg & (PCI_HP_IRQ_SLOT_N_PENDING<<slot)) ? "Yes" : "No"); + } + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_SERR_LOCATOR_REG); + + PCIE_DBG("Arbiter SERR Pending : %s\n", + (reg & PCI_HP_IRQ_SERR_ARBITER_PENDING) ? "Yes" : "No"); + + for (slot = 0; slot < numSlots; slot++) { + PCIE_DBG("Slot %d SERR Pending : %s\n", + slot+1, (reg & + (PCI_HP_IRQ_SERR_SLOT_N_PENDING<<slot)) ? "Yes" : "No"); + } + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG); + + PCIE_DBG("Global Interrupt Mask : %s\n", + (reg & PCI_HP_SERR_INT_GLOBAL_IRQ_MASK) ? "Yes" : "No"); + + PCIE_DBG("Global SERR Mask : %s\n", + (reg & PCI_HP_SERR_INT_GLOBAL_SERR_MASK) ? "Yes" : "No"); + + PCIE_DBG("Command Completion Interrupt Mask : %s\n", + (reg & PCI_HP_SERR_INT_CMD_COMPLETE_MASK) ? "Yes" : "No"); + + PCIE_DBG("Arbiter SERR Mask : %s\n", + (reg & PCI_HP_SERR_INT_ARBITER_SERR_MASK) ? "Yes" : "No"); + + PCIE_DBG("Command Completion Detected : %s\n", + (reg & PCI_HP_SERR_INT_CMD_COMPLETE_IRQ) ? "Yes" : "No"); + + PCIE_DBG("Arbiter Timeout Detected : %s\n", + (reg & PCI_HP_SERR_INT_ARBITER_IRQ) ? "Yes" : "No"); + + for (slot = 0; slot < numSlots; slot++) { + PCIE_DBG("Logical Slot %d Registers:\n", slot+1); + PCIE_DBG("------------------------------------\n"); + + reg = pcishpc_read_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot); + + PCIE_DBG("Slot %d state : %s\n", slot+1, + pcishpc_slot_textslotstate(pcishpc_slot_shpc_to_hpc(reg))); + + PCIE_DBG("Slot %d Power Indicator State : %s\n", slot+1, + pcishpc_slot_textledstate(pcishpc_led_shpc_to_hpc( + (reg>>2) &3))); + + PCIE_DBG("Slot %d Attention Indicator State : %s\n", slot+1, + pcishpc_slot_textledstate(pcishpc_led_shpc_to_hpc( + (reg>>4)&3))); + + PCIE_DBG("Slot %d Power Fault : %s\n", slot+1, + ((reg>>6)&1) ? "Fault Detected" : "No Fault"); + PCIE_DBG("Slot %d Attention Button : %s\n", slot+1, + ((reg>>7)&1) ? "Depressed" : "Not Depressed"); + PCIE_DBG("Slot %d MRL Sensor : %s\n", slot+1, + ((reg>>8)&1) ? "Not Closed" : "Closed"); + PCIE_DBG("Slot %d 66mhz Capable : %s\n", slot+1, + ((reg>>9)&1) ? "66mhz" : "33mgz"); + + switch ((reg>>10)&3) { + case 0: + state = "Card Present 7.5W"; + break; + case 1: + state = "Card Present 15W"; + break; + case 2: + state = "Card Present 25W"; + break; + case 3: + state = "Slot Empty"; + break; + } + + PCIE_DBG("Slot %d PRSNT1#/PRSNT2# : %s\n", slot+1, + state); + + switch ((reg>>12)&3) { + case 0: + state = "Non PCI-X"; + break; + case 1: + state = "66mhz PCI-X"; + break; + case 2: + state = "Reserved"; + break; + case 3: + state = "133mhz PCI-X"; + break; + } + + PCIE_DBG("Slot %d Card Presence Change Detected : %s\n", + slot+1, (reg & PCI_HP_SLOT_PRESENCE_DETECTED) ? "Yes" : + "No"); + PCIE_DBG("Slot %d Isolated Power Fault Detected : %s\n", + slot+1, (reg & PCI_HP_SLOT_ISO_PWR_DETECTED) ? "Yes" : + "No"); + PCIE_DBG("Slot %d Attention Button Press Detected : %s\n", + slot+1, (reg & PCI_HP_SLOT_ATTN_DETECTED) ? "Yes" : "No"); + PCIE_DBG("Slot %d MRL Sensor Change Detected : %s\n", + slot+1, (reg & PCI_HP_SLOT_MRL_DETECTED) ? "Yes" : "No"); + PCIE_DBG("Slot %d Connected Power Fault Detected : %s\n", + slot+1, (reg & PCI_HP_SLOT_POWER_DETECTED) ? "Yes" : "No"); + + PCIE_DBG("Slot %d Card Presence IRQ Masked : %s\n", + slot+1, (reg & PCI_HP_SLOT_PRESENCE_MASK) ? "Yes" : "No"); + PCIE_DBG("Slot %d Isolated Power Fault IRQ Masked : %s\n", + slot+1, (reg & PCI_HP_SLOT_ISO_PWR_MASK) ? "Yes" : "No"); + PCIE_DBG("Slot %d Attention Button IRQ Masked : %s\n", + slot+1, (reg & PCI_HP_SLOT_ATTN_MASK) ? "Yes" : "No"); + PCIE_DBG("Slot %d MRL Sensor IRQ Masked : %s\n", + slot+1, (reg & PCI_HP_SLOT_MRL_MASK) ? "Yes" : "No"); + PCIE_DBG("Slot %d Connected Power Fault IRQ Masked : %s\n", + slot+1, (reg & PCI_HP_SLOT_POWER_MASK) ? "Yes" : "No"); + PCIE_DBG("Slot %d MRL Sensor SERR Masked : %s\n", + slot+1, (reg & PCI_HP_SLOT_MRL_SERR_MASK) ? "Yes" : "No"); + PCIE_DBG("Slot %d Connected Power Fault SERR Masked : %s\n", + slot+1, (reg & PCI_HP_SLOT_POWER_SERR_MASK) ? "Yes" : "No"); + } +} +#endif /* DEBUG */ diff --git a/usr/src/uts/common/io/pciex/pcie.c b/usr/src/uts/common/io/pciex/pcie.c index 4f39719d6b..bf08608fa4 100644 --- a/usr/src/uts/common/io/pciex/pcie.c +++ b/usr/src/uts/common/io/pciex/pcie.c @@ -34,10 +34,15 @@ #include <sys/fm/util.h> #include <sys/promif.h> #include <sys/disp.h> -#include <sys/pcie.h> +#include <sys/stat.h> +#include <sys/file.h> #include <sys/pci_cap.h> +#include <sys/pci_impl.h> #include <sys/pcie_impl.h> +#include <sys/hotplug/pci/pcie_hp.h> +#include <sys/hotplug/pci/pcicfg.h> +/* Local functions prototypes */ static void pcie_init_pfd(dev_info_t *); static void pcie_fini_pfd(dev_info_t *); @@ -48,6 +53,7 @@ static void pcie_check_io_mem_range(ddi_acc_handle_t, boolean_t *, boolean_t *); #ifdef DEBUG uint_t pcie_debug_flags = 0; static void pcie_print_bus(pcie_bus_t *bus_p); +void pcie_dbg(char *fmt, ...); #endif /* DEBUG */ /* Variable to control default PCI-Express config settings */ @@ -114,7 +120,7 @@ uint32_t pcie_ecrc_value = * If a particular platform wants to disable certain errors such as UR/MA, * instead of using #defines have the platform's PCIe Root Complex driver set * these masks using the pcie_get_XXX_mask and pcie_set_XXX_mask functions. For - * x86 the closest thing to a PCIe root complex driver is NPE. For SPARC the + * x86 the closest thing to a PCIe root complex driver is NPE. For SPARC the * closest PCIe root complex driver is PX. * * pcie_serr_disable_flag : disable SERR only (in RCR and command reg) x86 @@ -135,13 +141,14 @@ uint32_t pcie_aer_suce_severity = PCIE_AER_SUCE_SERR_ASSERT | \ PCIE_AER_SUCE_USC_MSG_DATA_ERR; int pcie_max_mps = PCIE_DEVCTL_MAX_PAYLOAD_4096 >> 5; +int pcie_disable_ari = 0; static void pcie_scan_mps(dev_info_t *rc_dip, dev_info_t *dip, int *max_supported); static int pcie_get_max_supported(dev_info_t *dip, void *arg); static int pcie_map_phys(dev_info_t *dip, pci_regspec_t *phys_spec, caddr_t *addrp, ddi_acc_handle_t *handlep); -static void pcie_unmap_phys(ddi_acc_handle_t *handlep, pci_regspec_t *ph); +static void pcie_unmap_phys(ddi_acc_handle_t *handlep, pci_regspec_t *ph); /* * modload support @@ -149,7 +156,7 @@ static void pcie_unmap_phys(ddi_acc_handle_t *handlep, pci_regspec_t *ph); static struct modlmisc modlmisc = { &mod_miscops, /* Type of module */ - "PCIE: PCI framework" + "PCI Express Framework Module" }; static struct modlinkage modlinkage = { @@ -199,6 +206,195 @@ _info(struct modinfo *modinfop) return (mod_info(&modlinkage, modinfop)); } +/* ARGSUSED */ +int +pcie_init(dev_info_t *dip, caddr_t arg) +{ + int ret = DDI_SUCCESS; + + /* + * Create a "devctl" minor node to support DEVCTL_DEVICE_* + * and DEVCTL_BUS_* ioctls to this bus. + */ + if ((ret = ddi_create_minor_node(dip, "devctl", S_IFCHR, + PCI_MINOR_NUM(ddi_get_instance(dip), PCI_DEVCTL_MINOR), + DDI_NT_NEXUS, 0)) != DDI_SUCCESS) { + PCIE_DBG("Failed to create devctl minor node for %s%d\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + + return (ret); + } + + if ((ret = pcie_hp_init(dip, arg)) != DDI_SUCCESS) { + /* + * On a few x86 platforms, we observed unexpected hotplug + * initialization failures in recent years. Continue with + * a message printed because we don't want to stop PCI + * driver attach and system boot because of this hotplug + * initialization failure before we address all those issues. + */ + cmn_err(CE_WARN, "%s%d: Failed setting hotplug framework\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + +#if defined(__sparc) + ddi_remove_minor_node(dip, "devctl"); + + return (ret); +#endif /* defined(__sparc) */ + } + + if ((pcie_ari_supported(dip) == PCIE_ARI_FORW_SUPPORTED) && + (pcie_ari_is_enabled(dip) == PCIE_ARI_FORW_DISABLED)) + (void) pcicfg_configure(dip, 0, PCICFG_ALL_FUNC, + PCICFG_FLAG_ENABLE_ARI); + + return (DDI_SUCCESS); +} + +/* ARGSUSED */ +int +pcie_uninit(dev_info_t *dip) +{ + int ret = DDI_SUCCESS; + + if (pcie_ari_is_enabled(dip) == PCIE_ARI_FORW_ENABLED) + (void) pcie_ari_disable(dip); + + if ((ret = pcie_hp_uninit(dip)) != DDI_SUCCESS) { + PCIE_DBG("Failed to uninitialize hotplug for %s%d\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + + return (ret); + } + + ddi_remove_minor_node(dip, "devctl"); + + return (ret); +} + +/* ARGSUSED */ +int +pcie_intr(dev_info_t *dip) +{ + return (pcie_hp_intr(dip)); +} + +/* ARGSUSED */ +int +pcie_open(dev_info_t *dip, dev_t *devp, int flags, int otyp, cred_t *credp) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + + /* + * Make sure the open is for the right file type. + */ + if (otyp != OTYP_CHR) + return (EINVAL); + + /* + * Handle the open by tracking the device state. + */ + if ((bus_p->bus_soft_state == PCI_SOFT_STATE_OPEN_EXCL) || + ((flags & FEXCL) && + (bus_p->bus_soft_state != PCI_SOFT_STATE_CLOSED))) { + return (EBUSY); + } + + if (flags & FEXCL) + bus_p->bus_soft_state = PCI_SOFT_STATE_OPEN_EXCL; + else + bus_p->bus_soft_state = PCI_SOFT_STATE_OPEN; + + return (0); +} + +/* ARGSUSED */ +int +pcie_close(dev_info_t *dip, dev_t dev, int flags, int otyp, cred_t *credp) +{ + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + + if (otyp != OTYP_CHR) + return (EINVAL); + + bus_p->bus_soft_state = PCI_SOFT_STATE_CLOSED; + + return (0); +} + +/* ARGSUSED */ +int +pcie_ioctl(dev_info_t *dip, dev_t dev, int cmd, intptr_t arg, int mode, + cred_t *credp, int *rvalp) +{ + struct devctl_iocdata *dcp; + uint_t bus_state; + int rv = DDI_SUCCESS; + + /* + * We can use the generic implementation for devctl ioctl + */ + switch (cmd) { + case DEVCTL_DEVICE_GETSTATE: + case DEVCTL_DEVICE_ONLINE: + case DEVCTL_DEVICE_OFFLINE: + case DEVCTL_BUS_GETSTATE: + return (ndi_devctl_ioctl(dip, cmd, arg, mode, 0)); + default: + break; + } + + /* + * read devctl ioctl data + */ + if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS) + return (EFAULT); + + switch (cmd) { + case DEVCTL_BUS_QUIESCE: + if (ndi_get_bus_state(dip, &bus_state) == NDI_SUCCESS) + if (bus_state == BUS_QUIESCED) + break; + (void) ndi_set_bus_state(dip, BUS_QUIESCED); + break; + case DEVCTL_BUS_UNQUIESCE: + if (ndi_get_bus_state(dip, &bus_state) == NDI_SUCCESS) + if (bus_state == BUS_ACTIVE) + break; + (void) ndi_set_bus_state(dip, BUS_ACTIVE); + break; + case DEVCTL_BUS_RESET: + case DEVCTL_BUS_RESETALL: + case DEVCTL_DEVICE_RESET: + rv = ENOTSUP; + break; + default: + rv = ENOTTY; + } + + ndi_dc_freehdl(dcp); + return (rv); +} + +/* ARGSUSED */ +int +pcie_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, + int flags, char *name, caddr_t valuep, int *lengthp) +{ + if (dev == DDI_DEV_T_ANY) + goto skip; + + if (PCIE_IS_HOTPLUG_CAPABLE(dip) && + strcmp(name, "pci-occupant") == 0) { + int pci_dev = PCI_MINOR_NUM_TO_PCI_DEVNUM(getminor(dev)); + + pcie_hp_create_occupant_props(dip, dev, pci_dev); + } + +skip: + return (ddi_prop_op(dev, dip, prop_op, flags, name, valuep, lengthp)); +} + /* * PCI-Express child device initialization. * This function enables generic pci-express interrupts and error @@ -312,6 +508,13 @@ pcie_initchild(dev_info_t *cdip) pcie_enable_errors(cdip); } + bus_p->bus_ari = B_FALSE; + if ((pcie_ari_is_enabled(ddi_get_parent(cdip)) + == PCIE_ARI_FORW_ENABLED) && (pcie_ari_device(cdip) + == PCIE_ARI_DEVICE)) { + bus_p->bus_ari = B_TRUE; + } + if (pcie_initchild_mps(cdip) == DDI_FAILURE) return (DDI_FAILURE); @@ -528,6 +731,7 @@ void pcie_rc_fini_bus(dev_info_t *dip) { pcie_bus_t *bus_p = (pcie_bus_t *)ndi_get_bus_private(dip, B_FALSE); + ndi_set_bus_private(dip, B_FALSE, NULL, NULL); kmem_free(bus_p, sizeof (pcie_bus_t)); } @@ -552,7 +756,6 @@ pcie_init_bus(dev_info_t *cdip) /* allocate memory for pcie bus data */ bus_p = kmem_zalloc(sizeof (pcie_bus_t), KM_SLEEP); - /* Set back pointer to dip */ bus_p->bus_dip = cdip; @@ -561,8 +764,10 @@ pcie_init_bus(dev_info_t *cdip) errstr = "Cannot setup config access"; goto fail; } + bus_p->bus_cfg_hdl = eh; bus_p->bus_fm_flags = 0; + bus_p->bus_soft_state = PCI_SOFT_STATE_CLOSED; /* get device's bus/dev/function number */ if (pcie_get_bdf_from_dip(cdip, &bus_p->bus_bdf) != DDI_SUCCESS) { @@ -588,6 +793,14 @@ pcie_init_bus(dev_info_t *cdip) if (PCI_CAP_LOCATE(eh, PCI_CAP_XCFG_SPC(PCIE_EXT_CAP_ID_AER), &bus_p->bus_aer_off) != DDI_SUCCESS) bus_p->bus_aer_off = NULL; + + /* Check and save PCIe hotplug capability information */ + if ((PCIE_IS_RP(bus_p) || PCIE_IS_SWD(bus_p)) && + (PCI_CAP_GET16(eh, NULL, bus_p->bus_pcie_off, PCIE_PCIECAP) + & PCIE_PCIECAP_SLOT_IMPL) && + (PCI_CAP_GET32(eh, NULL, bus_p->bus_pcie_off, PCIE_SLOTCAP) + & PCIE_SLOTCAP_HP_CAPABLE)) + bus_p->bus_hp_sup_modes |= PCIE_NATIVE_HP_MODE; } else { bus_p->bus_pcie_off = NULL; bus_p->bus_dev_type = PCIE_PCIECAP_DEV_TYPE_PCI_PSEUDO; @@ -608,6 +821,11 @@ pcie_init_bus(dev_info_t *cdip) /* Save the Range information if device is a switch/bridge */ if (PCIE_IS_BDG(bus_p)) { + /* Check and save PCI hotplug (SHPC) capability information */ + if ((PCI_CAP_LOCATE(eh, PCI_CAP_ID_PCI_HOTPLUG, + &bus_p->bus_pci_hp_off)) == DDI_SUCCESS) + bus_p->bus_hp_sup_modes |= PCIE_PCI_HP_MODE; + /* get "bus_range" property */ range_size = sizeof (pci_bus_range_t); if (ddi_getlongprop_buf(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS, @@ -661,6 +879,10 @@ pcie_init_bus(dev_info_t *cdip) ndi_set_bus_private(cdip, B_TRUE, DEVI_PORT_TYPE_PCI, (void *)bus_p); + if (PCIE_IS_HOTPLUG_CAPABLE(cdip)) + (void) ndi_prop_create_boolean(DDI_DEV_T_NONE, cdip, + "hotplug-capable"); + pcie_init_pfd(cdip); bus_p->bus_mps = 0; @@ -717,6 +939,10 @@ pcie_fini_bus(dev_info_t *cdip) bus_p = PCIE_DIP2UPBUS(cdip); ASSERT(bus_p); + + if (PCIE_IS_HOTPLUG_CAPABLE(cdip)) + (void) ndi_prop_remove(DDI_DEV_T_NONE, cdip, "hotplug-capable"); + pci_config_teardown(&bus_p->bus_cfg_hdl); ndi_set_bus_private(cdip, B_TRUE, NULL, NULL); kmem_free(bus_p->bus_assigned_addr, @@ -1025,7 +1251,7 @@ pcie_get_bdf_for_dma_xfer(dev_info_t *dip, dev_info_t *rdip) /* * As part of the probing, the PCI fcode interpreter may setup a DMA * request if a given card has a fcode on it using dip and rdip of the - * AP (attachment point) i.e, dip and rdip of px/pcieb driver. In this + * hotplug connector i.e, dip and rdip of px/pcieb driver. In this * case, return a invalid value for the bdf since we cannot get to the * bdf value of the actual device which will be initiating this DMA. */ @@ -1152,6 +1378,7 @@ pcie_initchild_mps(dev_info_t *cdip) int max_payload_size; pcie_bus_t *bus_p; dev_info_t *pdip = ddi_get_parent(cdip); + uint8_t dev_type; bus_p = PCIE_DIP2BUS(cdip); if (bus_p == NULL) { @@ -1160,6 +1387,21 @@ pcie_initchild_mps(dev_info_t *cdip) return (DDI_FAILURE); } + dev_type = bus_p->bus_dev_type; + + /* + * For ARI Devices, only function zero's MPS needs to be set. + */ + if ((dev_type == PCIE_PCIECAP_DEV_TYPE_PCIE_DEV) && + (pcie_ari_is_enabled(pdip) == PCIE_ARI_FORW_ENABLED)) { + pcie_req_id_t child_bdf; + + if (pcie_get_bdf_from_dip(cdip, &child_bdf) == DDI_FAILURE) + return (DDI_FAILURE); + if ((child_bdf & PCIE_REQ_ID_ARI_FUNC_MASK) != 0) + return (DDI_SUCCESS); + } + if (PCIE_IS_RP(bus_p)) { /* * If this device is a root port, then the mps scan @@ -1202,6 +1444,7 @@ pcie_initchild_mps(dev_info_t *cdip) bus_p->bus_mps = mps; } + return (DDI_SUCCESS); } @@ -1244,6 +1487,7 @@ pcie_scan_mps(dev_info_t *rc_dip, dev_info_t *dip, int *max_supported) ddi_walk_devs(dip, pcie_get_max_supported, (void *)&max_pay_load_supported); ndi_devi_exit(ddi_get_parent(dip), circular_count); + *max_supported = max_pay_load_supported.highest_common_mps; } @@ -1313,7 +1557,7 @@ fail1: * dip - dip of root complex * * Returns - DDI_SUCCESS if there is at least one root port otherwise - * DDI_FAILURE. + * DDI_FAILURE. */ int pcie_root_port(dev_info_t *dip) @@ -1473,6 +1717,211 @@ pcie_get_rber_fatal(dev_info_t *dip) return (rp_bus_p->bus_pfd->pe_rber_fatal); } +int +pcie_ari_supported(dev_info_t *dip) +{ + uint32_t devcap2; + uint16_t pciecap; + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + uint8_t dev_type; + + PCIE_DBG("pcie_ari_supported: dip=%p\n", dip); + + if (bus_p == NULL) + return (PCIE_ARI_FORW_NOT_SUPPORTED); + + dev_type = bus_p->bus_dev_type; + + if ((dev_type != PCIE_PCIECAP_DEV_TYPE_DOWN) && + (dev_type != PCIE_PCIECAP_DEV_TYPE_ROOT)) + return (PCIE_ARI_FORW_NOT_SUPPORTED); + + if (pcie_disable_ari) { + PCIE_DBG("pcie_ari_supported: dip=%p: ARI Disabled\n", dip); + return (PCIE_ARI_FORW_NOT_SUPPORTED); + } + + pciecap = PCIE_CAP_GET(16, bus_p, PCIE_PCIECAP); + + if ((pciecap & PCIE_PCIECAP_VER_MASK) < PCIE_PCIECAP_VER_2_0) { + PCIE_DBG("pcie_ari_supported: dip=%p: Not 2.0\n", dip); + return (PCIE_ARI_FORW_NOT_SUPPORTED); + } + + devcap2 = PCIE_CAP_GET(32, bus_p, PCIE_DEVCAP2); + + PCIE_DBG("pcie_ari_supported: dip=%p: DevCap2=0x%x\n", + dip, devcap2); + + if (devcap2 & PCIE_DEVCAP2_ARI_FORWARD) { + PCIE_DBG("pcie_ari_supported: " + "dip=%p: ARI Forwarding is supported\n", dip); + return (PCIE_ARI_FORW_SUPPORTED); + } + return (PCIE_ARI_FORW_NOT_SUPPORTED); +} + +int +pcie_ari_enable(dev_info_t *dip) +{ + uint16_t devctl2; + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + + PCIE_DBG("pcie_ari_enable: dip=%p\n", dip); + + if (pcie_ari_supported(dip) == PCIE_ARI_FORW_NOT_SUPPORTED) + return (DDI_FAILURE); + + devctl2 = PCIE_CAP_GET(16, bus_p, PCIE_DEVCTL2); + devctl2 |= PCIE_DEVCTL2_ARI_FORWARD_EN; + PCIE_CAP_PUT(16, bus_p, PCIE_DEVCTL2, devctl2); + + PCIE_DBG("pcie_ari_enable: dip=%p: writing 0x%x to DevCtl2\n", + dip, devctl2); + + return (DDI_SUCCESS); +} + +int +pcie_ari_disable(dev_info_t *dip) +{ + uint16_t devctl2; + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + + PCIE_DBG("pcie_ari_disable: dip=%p\n", dip); + + if (pcie_ari_supported(dip) == PCIE_ARI_FORW_NOT_SUPPORTED) + return (DDI_FAILURE); + + devctl2 = PCIE_CAP_GET(16, bus_p, PCIE_DEVCTL2); + devctl2 &= ~PCIE_DEVCTL2_ARI_FORWARD_EN; + PCIE_CAP_PUT(16, bus_p, PCIE_DEVCTL2, devctl2); + + PCIE_DBG("pcie_ari_disable: dip=%p: writing 0x%x to DevCtl2\n", + dip, devctl2); + + return (DDI_SUCCESS); +} + +int +pcie_ari_is_enabled(dev_info_t *dip) +{ + uint16_t devctl2; + pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); + + PCIE_DBG("pcie_ari_is_enabled: dip=%p\n", dip); + + if (pcie_ari_supported(dip) == PCIE_ARI_FORW_NOT_SUPPORTED) + return (PCIE_ARI_FORW_DISABLED); + + devctl2 = PCIE_CAP_GET(32, bus_p, PCIE_DEVCTL2); + + PCIE_DBG("pcie_ari_is_enabled: dip=%p: DevCtl2=0x%x\n", + dip, devctl2); + + if (devctl2 & PCIE_DEVCTL2_ARI_FORWARD_EN) { + PCIE_DBG("pcie_ari_is_enabled: " + "dip=%p: ARI Forwarding is enabled\n", dip); + return (PCIE_ARI_FORW_ENABLED); + } + + return (PCIE_ARI_FORW_DISABLED); +} + +int +pcie_ari_device(dev_info_t *dip) +{ + ddi_acc_handle_t handle; + uint16_t cap_ptr; + + PCIE_DBG("pcie_ari_device: dip=%p\n", dip); + + /* + * XXX - This function may be called before the bus_p structure + * has been populated. This code can be changed to remove + * pci_config_setup()/pci_config_teardown() when the RFE + * to populate the bus_p structures early in boot is putback. + */ + + /* First make sure it is a PCIe device */ + + if (pci_config_setup(dip, &handle) != DDI_SUCCESS) + return (PCIE_NOT_ARI_DEVICE); + + if ((PCI_CAP_LOCATE(handle, PCI_CAP_ID_PCI_E, &cap_ptr)) + != DDI_SUCCESS) { + pci_config_teardown(&handle); + return (PCIE_NOT_ARI_DEVICE); + } + + /* Locate the ARI Capability */ + + if ((PCI_CAP_LOCATE(handle, PCI_CAP_XCFG_SPC(PCIE_EXT_CAP_ID_ARI), + &cap_ptr)) == DDI_FAILURE) { + pci_config_teardown(&handle); + return (PCIE_NOT_ARI_DEVICE); + } + + /* ARI Capability was found so it must be a ARI device */ + PCIE_DBG("pcie_ari_device: ARI Device dip=%p\n", dip); + + pci_config_teardown(&handle); + return (PCIE_ARI_DEVICE); +} + +int +pcie_ari_get_next_function(dev_info_t *dip, int *func) +{ + uint32_t val; + uint16_t cap_ptr, next_function; + ddi_acc_handle_t handle; + + /* + * XXX - This function may be called before the bus_p structure + * has been populated. This code can be changed to remove + * pci_config_setup()/pci_config_teardown() when the RFE + * to populate the bus_p structures early in boot is putback. + */ + + if (pci_config_setup(dip, &handle) != DDI_SUCCESS) + return (DDI_FAILURE); + + if ((PCI_CAP_LOCATE(handle, + PCI_CAP_XCFG_SPC(PCIE_EXT_CAP_ID_ARI), &cap_ptr)) == DDI_FAILURE) { + pci_config_teardown(&handle); + return (DDI_FAILURE); + } + + val = PCI_CAP_GET32(handle, NULL, cap_ptr, PCIE_ARI_CAP); + + next_function = (val >> PCIE_ARI_CAP_NEXT_FUNC_SHIFT) & + PCIE_ARI_CAP_NEXT_FUNC_MASK; + + pci_config_teardown(&handle); + + *func = next_function; + + return (DDI_SUCCESS); +} + +dev_info_t * +pcie_func_to_dip(dev_info_t *dip, pcie_req_id_t function) +{ + pcie_req_id_t child_bdf; + dev_info_t *cdip; + + for (cdip = ddi_get_child(dip); cdip; + cdip = ddi_get_next_sibling(cdip)) { + + if (pcie_get_bdf_from_dip(cdip, &child_bdf) == DDI_FAILURE) + return (NULL); + + if ((child_bdf & PCIE_REQ_ID_ARI_FUNC_MASK) == function) + return (cdip); + } + return (NULL); +} + #ifdef DEBUG static void diff --git a/usr/src/uts/common/io/pciex/pcieb.c b/usr/src/uts/common/io/pciex/pcieb.c index 298e895044..24d8ebc03d 100644 --- a/usr/src/uts/common/io/pciex/pcieb.c +++ b/usr/src/uts/common/io/pciex/pcieb.c @@ -39,18 +39,16 @@ #include <sys/sunddi.h> #include <sys/sunndi.h> #include <sys/fm/util.h> -#include <sys/pcie.h> #include <sys/pci_cap.h> +#include <sys/pci_impl.h> #include <sys/pcie_impl.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 <sys/promif.h> /* prom_printf */ #include <sys/disp.h> #include <sys/pcie_pwr.h> +#include <sys/hotplug/pci/pcie_hp.h> #include "pcieb.h" #ifdef PX_PLX #include <io/pciex/pcieb_plx.h> @@ -120,14 +118,13 @@ static struct bus_ops pcieb_bus_ops = { 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)(); */ + pcieb_intr_ops, /* (*bus_intr_op)(); */ + pcie_hp_common_ops /* (*bus_hp_op)(); */ }; static int pcieb_open(dev_t *, int, int, cred_t *); static int pcieb_close(dev_t, int, int, cred_t *); static int pcieb_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); -static int pcieb_prop_op(dev_t, dev_info_t *, ddi_prop_op_t, int, char *, - caddr_t, int *); static int pcieb_info(dev_info_t *, ddi_info_cmd_t, void *, void **); static uint_t pcieb_intr_handler(caddr_t arg1, caddr_t arg2); @@ -138,9 +135,6 @@ static void pcieb_pwr_teardown(dev_info_t *dip); static int pcieb_pwr_disable(dev_info_t *dip); /* Hotplug related functions */ -static int pcieb_pciehpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle); -static int pcieb_pcishpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle); -static int pcieb_init_hotplug(pcieb_devstate_t *pcieb); static void pcieb_id_props(pcieb_devstate_t *pcieb); /* @@ -161,7 +155,7 @@ static struct cb_ops pcieb_cb_ops = { nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ - pcieb_prop_op, /* cb_prop_op */ + pcie_prop_op, /* cb_prop_op */ NULL, /* streamtab */ D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */ CB_REV, /* rev */ @@ -194,7 +188,7 @@ static struct dev_ops pcieb_ops = { static struct modldrv modldrv = { &mod_driverops, /* Type of module */ - "PCIe to PCI nexus driver", + "PCIe bridge/switch driver", &pcieb_ops, /* driver ops */ }; @@ -246,6 +240,36 @@ _info(struct modinfo *modinfop) return (mod_info(&modlinkage, modinfop)); } +/* ARGSUSED */ +static int +pcieb_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) +{ + minor_t minor = getminor((dev_t)arg); + int instance = PCI_MINOR_NUM_TO_INSTANCE(minor); + pcieb_devstate_t *pcieb = ddi_get_soft_state(pcieb_state, instance); + int ret = DDI_SUCCESS; + + switch (infocmd) { + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)(intptr_t)instance; + break; + case DDI_INFO_DEVT2DEVINFO: + if (pcieb == NULL) { + ret = DDI_FAILURE; + break; + } + + *result = (void *)pcieb->pcieb_dip; + break; + default: + ret = DDI_FAILURE; + break; + } + + return (ret); +} + + /*ARGSUSED*/ static int pcieb_probe(dev_info_t *devi) @@ -261,7 +285,6 @@ pcieb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) pcieb_devstate_t *pcieb; pcie_bus_t *bus_p = PCIE_DIP2UPBUS(devi); ddi_acc_handle_t config_handle = bus_p->bus_cfg_hdl; - uint8_t dev_type = bus_p->bus_dev_type; switch (cmd) { case DDI_RESUME: @@ -297,7 +320,6 @@ pcieb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) return (DDI_FAILURE); pcieb = ddi_get_soft_state(pcieb_state, instance); pcieb->pcieb_dip = devi; - pcieb->pcieb_soft_state = PCIEB_SOFT_STATE_CLOSED; if ((pcieb_fm_init(pcieb)) != DDI_SUCCESS) { PCIEB_DEBUG(DBG_ATTACH, devi, "Failed in pcieb_fm_init\n"); @@ -356,37 +378,14 @@ pcieb_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) pcieb_attach_plx_workarounds(pcieb); #endif /* PX_PLX */ - /* Initialize hotplug */ - pcieb->pcieb_hotplug_capable = B_FALSE; - - if ((dev_type == PCIE_PCIECAP_DEV_TYPE_DOWN) || - (dev_type == PCIE_PCIECAP_DEV_TYPE_ROOT) || - (dev_type == PCIE_PCIECAP_DEV_TYPE_PCIE2PCI) || - (dev_type == PCIE_PCIECAP_DEV_TYPE_PCI2PCIE)) { - (void) pcieb_init_hotplug(pcieb); - } + if (pcie_init(devi, NULL) != DDI_SUCCESS) + goto fail; /* * Initialize interrupt handlers. Ignore return value. */ (void) pcieb_intr_attach(pcieb); - if (pcieb->pcieb_hotplug_capable == B_FALSE) { - /* - * (for non hotplug bus) this would create ":devctl" minor - * node to support DEVCTL_DEVICE_* and DEVCTL_BUS_* ioctls - * to this bus. - */ - 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; - } - - PCIEB_DEBUG(DBG_ATTACH, devi, - "pcieb_attach: this nexus %s hotplug slots\n", - pcieb->pcieb_hotplug_capable == B_TRUE ? "has":"has no"); - /* Do any platform specific workarounds needed at this time */ pcieb_plat_attach_workaround(devi); @@ -429,19 +428,8 @@ pcieb_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) /* remove interrupt handlers */ pcieb_intr_fini(pcieb); - if (pcieb->pcieb_hotplug_capable == B_TRUE) { - if (pcihp_uninit(devi) == DDI_FAILURE) - error = DDI_FAILURE; - - if (pcieb->pcieb_hpc_type == HPC_PCIE) - (void) pciehpc_uninit(devi); - else if (pcieb->pcieb_hpc_type == HPC_SHPC) - (void) pcishpc_uninit(devi); - - (void) ndi_prop_remove(DDI_DEV_T_NONE, devi, "hotplug-capable"); - } else { - ddi_remove_minor_node(devi, "devctl"); - } + /* uninitialize inband PCI-E HPC if present */ + (void) pcie_uninit(devi); (void) ddi_prop_remove(DDI_DEV_T_NONE, devi, "device_type"); @@ -640,7 +628,7 @@ static int pcieb_name_child(dev_info_t *child, char *name, int namelen) { pci_regspec_t *pci_rp; - uint_t slot, func; + uint_t device, func; char **unit_addr; uint_t n; @@ -677,13 +665,19 @@ pcieb_name_child(dev_info_t *child, char *name, int namelen) } /* copy the device identifications */ - slot = PCI_REG_DEV_G(pci_rp[0].pci_phys_hi); + device = PCI_REG_DEV_G(pci_rp[0].pci_phys_hi); func = PCI_REG_FUNC_G(pci_rp[0].pci_phys_hi); + if (pcie_ari_is_enabled(ddi_get_parent(child)) + == PCIE_ARI_FORW_ENABLED) { + func = (device << 3) | func; + device = 0; + } + if (func != 0) - (void) snprintf(name, namelen, "%x,%x", slot, func); + (void) snprintf(name, namelen, "%x,%x", device, func); else - (void) snprintf(name, namelen, "%x", slot); + (void) snprintf(name, namelen, "%x", device); ddi_prop_free(pci_rp); return (DDI_SUCCESS); @@ -911,7 +905,7 @@ pcieb_intr_init(pcieb_devstate_t *pcieb, int intr_type) (intr_type == DDI_INTR_TYPE_MSI) ? "MSI" : "INTx"); request = 0; - if (pcieb->pcieb_hotplug_capable) { + if (PCIE_IS_HOTPLUG_ENABLED(dip)) { request++; is_hp = B_TRUE; } @@ -1220,202 +1214,52 @@ pcieb_fm_fini(pcieb_devstate_t *pcieb_p) static int pcieb_open(dev_t *devp, int flags, int otyp, cred_t *credp) { - pcieb_devstate_t *pcieb_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); + int inst = PCI_MINOR_NUM_TO_INSTANCE(getminor(*devp)); + pcieb_devstate_t *pcieb = ddi_get_soft_state(pcieb_state, inst); + int rv; - /* - * Get the soft state structure for the device. - */ - pcieb_p = (pcieb_devstate_t *)ddi_get_soft_state(pcieb_state, - instance); - - if (pcieb_p == NULL) + if (pcieb == NULL) return (ENXIO); - if (pcieb_p->pcieb_hotplug_capable == B_TRUE) - return ((pcihp_get_cb_ops())->cb_open(devp, flags, - otyp, credp)); + mutex_enter(&pcieb->pcieb_mutex); + rv = pcie_open(pcieb->pcieb_dip, devp, flags, otyp, credp); + mutex_exit(&pcieb->pcieb_mutex); - /* - * Handle the open by tracking the device state. - */ - mutex_enter(&pcieb_p->pcieb_mutex); - if (flags & FEXCL) { - if (pcieb_p->pcieb_soft_state != PCIEB_SOFT_STATE_CLOSED) { - mutex_exit(&pcieb_p->pcieb_mutex); - return (EBUSY); - } - pcieb_p->pcieb_soft_state = PCIEB_SOFT_STATE_OPEN_EXCL; - } else { - if (pcieb_p->pcieb_soft_state == PCIEB_SOFT_STATE_OPEN_EXCL) { - mutex_exit(&pcieb_p->pcieb_mutex); - return (EBUSY); - } - pcieb_p->pcieb_soft_state = PCIEB_SOFT_STATE_OPEN; - } - mutex_exit(&pcieb_p->pcieb_mutex); - return (0); + return (rv); } static int pcieb_close(dev_t dev, int flags, int otyp, cred_t *credp) { - pcieb_devstate_t *pcieb_p; - minor_t minor = getminor(dev); - int instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor); + int inst = PCI_MINOR_NUM_TO_INSTANCE(getminor(dev)); + pcieb_devstate_t *pcieb = ddi_get_soft_state(pcieb_state, inst); + int rv; - if (otyp != OTYP_CHR) - return (EINVAL); - - pcieb_p = (pcieb_devstate_t *)ddi_get_soft_state(pcieb_state, - instance); - - if (pcieb_p == NULL) + if (pcieb == NULL) return (ENXIO); - if (pcieb_p->pcieb_hotplug_capable == B_TRUE) - return ((pcihp_get_cb_ops())->cb_close(dev, flags, - otyp, credp)); + mutex_enter(&pcieb->pcieb_mutex); + rv = pcie_close(pcieb->pcieb_dip, dev, flags, otyp, credp); + mutex_exit(&pcieb->pcieb_mutex); - mutex_enter(&pcieb_p->pcieb_mutex); - pcieb_p->pcieb_soft_state = PCIEB_SOFT_STATE_CLOSED; - mutex_exit(&pcieb_p->pcieb_mutex); - return (0); + return (rv); } static int pcieb_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { - pcieb_devstate_t *pcieb_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); - - pcieb_p = (pcieb_devstate_t *)ddi_get_soft_state(pcieb_state, - instance); - - if (pcieb_p == NULL) - return (ENXIO); - - self = pcieb_p->pcieb_dip; - if (pcieb_p->pcieb_hotplug_capable == B_TRUE) { - rv = ((pcihp_get_cb_ops())->cb_ioctl(dev, cmd, - arg, mode, credp, rvalp)); + int inst = PCI_MINOR_NUM_TO_INSTANCE(getminor(dev)); + pcieb_devstate_t *pcieb = ddi_get_soft_state(pcieb_state, inst); + int rv; - pcieb_plat_ioctl_hotplug(self, rv, cmd); - return (rv); - } - - /* - * 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 -pcieb_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, - int flags, char *name, caddr_t valuep, int *lengthp) -{ - pcieb_devstate_t *pcieb_p; - minor_t minor = getminor(dev); - int instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor); - - pcieb_p = (pcieb_devstate_t *)ddi_get_soft_state(pcieb_state, - instance); - - if (pcieb_p == NULL) + if (pcieb == NULL) return (ENXIO); - if (pcieb_p->pcieb_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)); -} - -/*ARGSUSED*/ -static int -pcieb_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) -{ - pcieb_devstate_t *pcieb_p; /* per pcieb state pointer */ - minor_t minor = getminor((dev_t)arg); - int instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor); - - pcieb_p = (pcieb_devstate_t *)ddi_get_soft_state(pcieb_state, - instance); + /* To handle devctl and hotplug related ioctls */ + rv = pcie_ioctl(pcieb->pcieb_dip, dev, cmd, arg, mode, credp, rvalp); - switch (infocmd) { - default: - return (DDI_FAILURE); - - case DDI_INFO_DEVT2INSTANCE: - *result = (void *)(intptr_t)instance; - return (DDI_SUCCESS); - - case DDI_INFO_DEVT2DEVINFO: - if (pcieb_p == NULL) - return (DDI_FAILURE); - *result = (void *)pcieb_p->pcieb_dip; - return (DDI_SUCCESS); - } + return (rv); } /* @@ -1444,12 +1288,8 @@ pcieb_intr_handler(caddr_t arg1, caddr_t arg2) if (isrc == PCIEB_INTR_SRC_UNKNOWN) goto FAIL; - if (isrc & PCIEB_INTR_SRC_HP) { - if (pcieb_p->pcieb_hpc_type == HPC_PCIE) - ret = pciehpc_intr(dip); - else if (pcieb_p->pcieb_hpc_type == HPC_SHPC) - ret = pcishpc_intr(dip); - } + if (isrc & PCIEB_INTR_SRC_HP) + ret = pcie_intr(dip); if (isrc & PCIEB_INTR_SRC_PME) ret = DDI_INTR_CLAIMED; @@ -1576,99 +1416,6 @@ pcieb_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, } -/*ARGSUSED*/ -static int pcieb_pciehpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle) -{ - uint16_t cap_ptr; - - if ((PCI_CAP_LOCATE(config_handle, PCI_CAP_ID_PCI_E, &cap_ptr)) != - DDI_FAILURE) { - uint16_t slotimpl = PCI_CAP_GET16(config_handle, NULL, cap_ptr, - PCIE_PCIECAP) & PCIE_PCIECAP_SLOT_IMPL; - if (slotimpl) - if (PCI_CAP_GET32(config_handle, NULL, cap_ptr, - PCIE_SLOTCAP) & PCIE_SLOTCAP_HP_CAPABLE) - return (DDI_SUCCESS); - } - - return (DDI_FAILURE); -} - -static int pcieb_pcishpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle) -{ - return (pcieb_plat_pcishpc_probe(dip, config_handle)); -} - -/* - * Initialize hotplug framework if we are hotpluggable. - * Sets flag in the soft state if Hot Plug is supported and initialized - * properly. - */ -/*ARGSUSED*/ -static int -pcieb_init_hotplug(pcieb_devstate_t *pcieb) -{ - int rv = DDI_FAILURE; - pcie_bus_t *bus_p = PCIE_DIP2BUS(pcieb->pcieb_dip); - ddi_acc_handle_t config_handle = bus_p->bus_cfg_hdl; - uint8_t dev_type = bus_p->bus_dev_type; - -#ifdef PX_PLX - uint16_t vid = bus_p->bus_dev_ven_id & 0xFFFF; - uint16_t did = bus_p->bus_dev_ven_id >> 16; - if ((vid == PXB_VENDOR_PLX) && (did == PXB_DEVICE_PLX_8532) && - (bus_p->bus_rev_id <= PXB_DEVICE_PLX_AA_REV)) - return (DDI_SUCCESS); -#endif /* PX_PLX */ - - if (((dev_type == PCIE_PCIECAP_DEV_TYPE_DOWN) || - (dev_type == PCIE_PCIECAP_DEV_TYPE_PCI2PCIE) || - (dev_type == PCIE_PCIECAP_DEV_TYPE_ROOT)) && - (pcieb_pciehpc_probe(pcieb->pcieb_dip, - config_handle) == DDI_SUCCESS)) { - pcieb->pcieb_hpc_type = HPC_PCIE; - } else if ((dev_type == PCIE_PCIECAP_DEV_TYPE_PCIE2PCI) && - (pcieb_pcishpc_probe(pcieb->pcieb_dip, - config_handle) == DDI_SUCCESS)) { - pcieb->pcieb_hpc_type = HPC_SHPC; - } else { - pcieb->pcieb_hpc_type = HPC_NONE; - return (DDI_SUCCESS); - } - - pcieb->pcieb_hotplug_capable = B_TRUE; - - if (pcieb->pcieb_hpc_type == HPC_PCIE) - rv = pciehpc_init(pcieb->pcieb_dip, NULL); - else if (pcieb->pcieb_hpc_type == HPC_SHPC) - rv = pcishpc_init(pcieb->pcieb_dip); - - if (rv != DDI_SUCCESS) - goto fail; - - if (pcihp_init(pcieb->pcieb_dip) != DDI_SUCCESS) { - if (pcieb->pcieb_hpc_type == HPC_PCIE) - (void) pciehpc_uninit(pcieb->pcieb_dip); - else if (pcieb->pcieb_hpc_type == HPC_SHPC) - (void) pcishpc_uninit(pcieb->pcieb_dip); - - goto fail; - } - - (void) ndi_prop_create_boolean(DDI_DEV_T_NONE, pcieb->pcieb_dip, - "hotplug-capable"); - - return (DDI_SUCCESS); - -fail: - pcieb->pcieb_hpc_type = HPC_NONE; - pcieb->pcieb_hotplug_capable = B_FALSE; - PCIEB_DEBUG(DBG_ATTACH, pcieb->pcieb_dip, "Failed setting hotplug" - " framework\n"); - - return (DDI_FAILURE); -} - /* * Power management related initialization specific to pcieb. * Called by pcieb_attach() @@ -1922,10 +1669,10 @@ pcieb_create_ranges_prop(dev_info_t *dip, ddi_acc_handle_t config_handle) { uint32_t base, limit; - pcieb_ranges_t ranges[PCIEB_RANGE_LEN]; + ppb_ranges_t ranges[PCIEB_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 (pcieb_ranges_t)/sizeof (int); + int i = 0, rangelen = sizeof (ppb_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); diff --git a/usr/src/uts/common/io/pciex/pcieb.h b/usr/src/uts/common/io/pciex/pcieb.h index 17a9052172..7d8556cfc1 100644 --- a/usr/src/uts/common/io/pciex/pcieb.h +++ b/usr/src/uts/common/io/pciex/pcieb.h @@ -70,7 +70,6 @@ typedef enum { /* same sequence as pcieb_debug_sym[] */ #define PX_MDT_11 0x01 #define PX_MDT_22 0x10 - #define NUM_LOGICAL_SLOTS 32 #define PCIEB_RANGE_LEN 2 #define PCIEB_32BIT_IO 1 @@ -82,30 +81,9 @@ typedef enum { /* same sequence as pcieb_debug_sym[] */ #define PCIEB_LADDR(lo, hi) (((uint16_t)(hi) << 16) | (uint16_t)(lo)) #define PCIEB_32bit_MEMADDR(addr) (PCIEB_LADDR(0, ((uint16_t)(addr) & 0xFFF0))) -/* - * The following typedef is used to represent an entry in the "ranges" - * property of a device node. - */ -typedef struct { - uint32_t child_high; - uint32_t child_mid; - uint32_t child_low; - uint32_t parent_high; - uint32_t parent_mid; - uint32_t parent_low; - uint32_t size_high; - uint32_t size_low; -} pcieb_ranges_t; - -typedef enum { HPC_NONE, HPC_PCIE, HPC_SHPC, HPC_OUTBAND } pcieb_hpc_type_t; - typedef struct { dev_info_t *pcieb_dip; - /* Hotplug support */ - boolean_t pcieb_hotplug_capable; - pcieb_hpc_type_t pcieb_hpc_type; - /* Interrupt support */ ddi_intr_handle_t *pcieb_htable; /* Intr Handlers */ int pcieb_htable_size; /* htable size */ @@ -114,7 +92,6 @@ typedef struct { int pcieb_intr_type; /* (MSI | FIXED) */ int pcieb_isr_tab[4]; /* MSI source offset */ - uint_t pcieb_soft_state; int pcieb_init_flags; kmutex_t pcieb_mutex; /* Soft state mutex */ kmutex_t pcieb_intr_mutex; /* Intr handler mutex */ @@ -156,13 +133,13 @@ extern void *pcieb_state; */ #define NVIDIA_VENDOR_ID 0x10de /* Nvidia Vendor Id */ -#ifdef BCM_SW_WORKAROUNDS +#ifdef PCIEB_BCM /* Workaround for address space limitation in Broadcom 5714/5715 */ #define PCIEB_ADDR_LIMIT_LO 0ull #define PCIEB_ADDR_LIMIT_HI ((1ull << 40) - 1) -#endif /* BCM_SW_WORKAROUNDS */ +#endif /* PCIEB_BCM */ /* * The following values are used to initialize the cache line size @@ -177,7 +154,6 @@ extern void pcieb_plat_attach_workaround(dev_info_t *dip); extern void pcieb_plat_intr_attach(pcieb_devstate_t *pcieb); extern void pcieb_plat_initchild(dev_info_t *child); extern void pcieb_plat_uninitchild(dev_info_t *child); -extern void pcieb_plat_ioctl_hotplug(dev_info_t *dip, int rv, int cmd); extern int pcieb_plat_ctlops(dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg); extern int pcieb_plat_pcishpc_probe(dev_info_t *dip, diff --git a/usr/src/uts/common/os/ddi_hp_impl.c b/usr/src/uts/common/os/ddi_hp_impl.c new file mode 100644 index 0000000000..6aeb01aac4 --- /dev/null +++ b/usr/src/uts/common/os/ddi_hp_impl.c @@ -0,0 +1,1139 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Sun DDI hotplug implementation specific functions + */ + +#include <sys/sysmacros.h> +#include <sys/types.h> +#include <sys/file.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kmem.h> +#include <sys/cmn_err.h> +#include <sys/debug.h> +#include <sys/avintr.h> +#include <sys/autoconf.h> +#include <sys/ddi.h> +#include <sys/sunndi.h> +#include <sys/ndi_impldefs.h> +#include <sys/sysevent.h> +#include <sys/sysevent/eventdefs.h> +#include <sys/sysevent/dr.h> +#include <sys/fs/dv_node.h> + +/* + * Local function prototypes + */ +/* Connector operations */ +static int ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state); +static int ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t new_state); +static int ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp); +static int ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, + boolean_t online); +static int ddihp_change_node_state(dev_info_t *dip, void *arg); +/* Port operations */ +static int ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state); +static int ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state); +static int ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state); +/* Misc routines */ +static void ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp); +static boolean_t ddihp_check_status_prop(dev_info_t *dip); + +/* + * Global functions (called within hotplug framework) + */ + +/* + * Implement modctl() commands for hotplug. + * Called by modctl_hp() in modctl.c + */ +int +ddihp_modctl(int hp_op, char *path, char *cn_name, uintptr_t arg, + uintptr_t rval) +{ + dev_info_t *dip; + ddi_hp_cn_handle_t *hdlp; + ddi_hp_op_t op = (ddi_hp_op_t)hp_op; + int count, rv, error; + + /* Get the dip of nexus node */ + dip = e_ddi_hold_devi_by_path(path, 0); + + if (dip == NULL) + return (ENXIO); + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: dip %p op %x path %s " + "cn_name %s arg %p rval %p\n", (void *)dip, hp_op, path, cn_name, + (void *)arg, (void *)rval)); + + if (!NEXUS_HAS_HP_OP(dip)) { + ddi_release_devi(dip); + return (ENOTSUP); + } + + /* Lock before access */ + ndi_devi_enter(dip, &count); + + hdlp = ddihp_cn_name_to_handle(dip, cn_name); + + if (hp_op == DDI_HPOP_CN_CREATE_PORT) { + if (hdlp != NULL) { + /* this port already exists. */ + error = EEXIST; + + goto done; + } + rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( + dip, cn_name, op, NULL, NULL); + } else { + if (hdlp == NULL) { + /* Invalid Connection name */ + error = ENXIO; + + goto done; + } + if (hp_op == DDI_HPOP_CN_CHANGE_STATE) { + ddi_hp_cn_state_t target_state = (ddi_hp_cn_state_t)arg; + ddi_hp_cn_state_t result_state = 0; + + DDIHP_CN_OPS(hdlp, op, (void *)&target_state, + (void *)&result_state, rv); + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: target_state=" + "%x, result_state=%x, rv=%x \n", + target_state, result_state, rv)); + } else { + DDIHP_CN_OPS(hdlp, op, (void *)arg, (void *)rval, rv); + } + } + switch (rv) { + case DDI_SUCCESS: + error = 0; + break; + case DDI_EINVAL: + error = EINVAL; + break; + case DDI_EBUSY: + error = EBUSY; + break; + case DDI_ENOTSUP: + error = ENOTSUP; + break; + case DDI_ENOMEM: + error = ENOMEM; + break; + default: + error = EIO; + } + +done: + ndi_devi_exit(dip, count); + + ddi_release_devi(dip); + + return (error); +} + +/* + * Return the state of Hotplug Connection (CN) + */ +int +ddihp_cn_getstate(ddi_hp_cn_handle_t *hdlp) +{ + ddi_hp_cn_state_t new_state; + int ret; + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: pdip %p hdlp %p\n", + (void *)hdlp->cn_dip, (void *)hdlp)); + + ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip)); + + DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_GET_STATE, + NULL, (void *)&new_state, ret); + if (ret != DDI_SUCCESS) { + DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: " + "CN %p getstate command failed\n", (void *)hdlp)); + + return (ret); + } + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: hdlp %p " + "current Connection state %x new Connection state %x\n", + (void *)hdlp, hdlp->cn_info.cn_state, new_state)); + + if (new_state != hdlp->cn_info.cn_state) { + hdlp->cn_info.cn_state = new_state; + ddihp_update_last_change(hdlp); + } + + return (ret); +} + +/* + * Implementation function for unregistering the Hotplug Connection (CN) + */ +int +ddihp_cn_unregister(ddi_hp_cn_handle_t *hdlp) +{ + dev_info_t *dip = hdlp->cn_dip; + + DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: hdlp %p\n", + (void *)hdlp)); + + ASSERT(DEVI_BUSY_OWNED(dip)); + + (void) ddihp_cn_getstate(hdlp); + + if (hdlp->cn_info.cn_state > DDI_HP_CN_STATE_OFFLINE) { + DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: dip %p, hdlp %p " + "state %x. Device busy, failed to unregister connection!\n", + (void *)dip, (void *)hdlp, hdlp->cn_info.cn_state)); + + return (DDI_EBUSY); + } + + /* unlink the handle */ + DDIHP_LIST_REMOVE(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), hdlp); + + kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1); + kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t)); + return (DDI_SUCCESS); +} + +/* + * For a given Connection name and the dip node where the Connection is + * supposed to be, find the corresponding hotplug handle. + */ +ddi_hp_cn_handle_t * +ddihp_cn_name_to_handle(dev_info_t *dip, char *cn_name) +{ + ddi_hp_cn_handle_t *hdlp; + + ASSERT(DEVI_BUSY_OWNED(dip)); + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " + "dip %p cn_name to find: %s", (void *)dip, cn_name)); + for (hdlp = DEVI(dip)->devi_hp_hdlp; hdlp; hdlp = hdlp->next) { + DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " + "current cn_name: %s", hdlp->cn_info.cn_name)); + + if (strcmp(cn_name, hdlp->cn_info.cn_name) == 0) { + /* found */ + return (hdlp); + } + } + DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: " + "failed to find cn_name")); + return (NULL); +} + +/* + * Process the hotplug operations for Connector and also create Port + * upon user command. + */ +int +ddihp_connector_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op, + void *arg, void *result) +{ + int rv = DDI_SUCCESS; + dev_info_t *dip = hdlp->cn_dip; + + ASSERT(DEVI_BUSY_OWNED(dip)); + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: pdip=%p op=%x " + "hdlp=%p arg=%p\n", (void *)dip, op, (void *)hdlp, arg)); + + if (op == DDI_HPOP_CN_CHANGE_STATE) { + ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; + + rv = ddihp_cn_pre_change_state(hdlp, target_state); + if (rv != DDI_SUCCESS) { + /* the state is not changed */ + *((ddi_hp_cn_state_t *)result) = + hdlp->cn_info.cn_state; + return (rv); + } + } + ASSERT(NEXUS_HAS_HP_OP(dip)); + rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( + dip, hdlp->cn_info.cn_name, op, arg, result); + + if (rv != DDI_SUCCESS) { + DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: " + "bus_hp_op failed: pdip=%p cn_name:%s op=%x " + "hdlp=%p arg=%p\n", (void *)dip, hdlp->cn_info.cn_name, + op, (void *)hdlp, arg)); + } + if (op == DDI_HPOP_CN_CHANGE_STATE) { + int rv_post; + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: " + "old_state=%x, new_state=%x, rv=%x\n", + hdlp->cn_info.cn_state, *(ddi_hp_cn_state_t *)result, rv)); + + /* + * After state change op is successfully done or + * failed at some stages, continue to do some jobs. + */ + rv_post = ddihp_cn_post_change_state(hdlp, + *(ddi_hp_cn_state_t *)result); + + if (rv_post != DDI_SUCCESS) + rv = rv_post; + } + + return (rv); +} + +/* + * Process the hotplug op for Port + */ +int +ddihp_port_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op, + void *arg, void *result) +{ + int ret = DDI_SUCCESS; + + ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip)); + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_ops: pdip=%p op=%x hdlp=%p " + "arg=%p\n", (void *)hdlp->cn_dip, op, (void *)hdlp, arg)); + + switch (op) { + case DDI_HPOP_CN_GET_STATE: + { + int state; + + state = hdlp->cn_info.cn_state; + + if (hdlp->cn_info.cn_child == NULL) { + /* No child. Either present or empty. */ + if (state >= DDI_HP_CN_STATE_PORT_PRESENT) + state = DDI_HP_CN_STATE_PORT_PRESENT; + else + state = DDI_HP_CN_STATE_PORT_EMPTY; + + } else { /* There is a child of this Port */ + + /* Check DEVI(dip)->devi_node_state */ + switch (i_ddi_node_state(hdlp->cn_info.cn_child)) { + case DS_INVAL: + case DS_PROTO: + case DS_LINKED: + case DS_BOUND: + case DS_INITIALIZED: + case DS_PROBED: + state = DDI_HP_CN_STATE_OFFLINE; + break; + case DS_ATTACHED: + state = DDI_HP_CN_STATE_MAINTENANCE; + break; + case DS_READY: + state = DDI_HP_CN_STATE_ONLINE; + break; + default: + /* should never reach here */ + ASSERT("unknown devinfo state"); + } + /* + * Check DEVI(dip)->devi_state in case the node is + * downgraded or quiesced. + */ + if (state == DDI_HP_CN_STATE_ONLINE && + ddi_get_devstate(hdlp->cn_info.cn_child) != + DDI_DEVSTATE_UP) + state = DDI_HP_CN_STATE_MAINTENANCE; + } + + *((ddi_hp_cn_state_t *)result) = state; + + break; + } + case DDI_HPOP_CN_CHANGE_STATE: + { + ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; + ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; + + ret = ddihp_port_change_state(hdlp, target_state); + if (curr_state != hdlp->cn_info.cn_state) { + ddihp_update_last_change(hdlp); + } + *((ddi_hp_cn_state_t *)result) = hdlp->cn_info.cn_state; + + break; + } + case DDI_HPOP_CN_REMOVE_PORT: + { + (void) ddihp_cn_getstate(hdlp); + + if (hdlp->cn_info.cn_state != DDI_HP_CN_STATE_PORT_EMPTY) { + /* Only empty PORT can be removed by commands */ + ret = DDI_EBUSY; + + break; + } + + ret = ddihp_cn_unregister(hdlp); + break; + } + default: + ret = DDI_ENOTSUP; + break; + } + + return (ret); +} + +/* + * Generate the system event with a possible hint + */ +/* ARGSUSED */ +void +ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_sysevent_t event_sub_class, int hint, int kmflag) +{ + dev_info_t *dip = hdlp->cn_dip; + char *cn_path, *ap_id; + char *ev_subclass = NULL; + nvlist_t *ev_attr_list = NULL; + sysevent_id_t eid; + int ap_id_len, err; + + cn_path = kmem_zalloc(MAXPATHLEN, kmflag); + if (cn_path == NULL) { + cmn_err(CE_WARN, + "%s%d: Failed to allocate memory for hotplug" + " connection: %s\n", + ddi_driver_name(dip), ddi_get_instance(dip), + hdlp->cn_info.cn_name); + + return; + } + + /* + * Minor device name will be bus path + * concatenated with connection name. + * One of consumers of the sysevent will pass it + * to cfgadm as AP ID. + */ + (void) strcpy(cn_path, "/devices"); + (void) ddi_pathname(dip, cn_path + strlen("/devices")); + + ap_id_len = strlen(cn_path) + strlen(":") + + strlen(hdlp->cn_info.cn_name) + 1; + ap_id = kmem_zalloc(ap_id_len, kmflag); + if (ap_id == NULL) { + cmn_err(CE_WARN, + "%s%d: Failed to allocate memory for AP ID: %s:%s\n", + ddi_driver_name(dip), ddi_get_instance(dip), + cn_path, hdlp->cn_info.cn_name); + kmem_free(cn_path, MAXPATHLEN); + + return; + } + + (void) strcpy(ap_id, cn_path); + (void) strcat(ap_id, ":"); + (void) strcat(ap_id, hdlp->cn_info.cn_name); + kmem_free(cn_path, MAXPATHLEN); + + err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag); + + if (err != 0) { + cmn_err(CE_WARN, + "%s%d: Failed to allocate memory for event subclass %d\n", + ddi_driver_name(dip), ddi_get_instance(dip), + event_sub_class); + kmem_free(ap_id, ap_id_len); + + return; + } + + switch (event_sub_class) { + case DDI_HP_CN_STATE_CHANGE: + ev_subclass = ESC_DR_AP_STATE_CHANGE; + + switch (hint) { + case SE_NO_HINT: /* fall through */ + case SE_HINT_INSERT: /* fall through */ + case SE_HINT_REMOVE: + err = nvlist_add_string(ev_attr_list, DR_HINT, + SE_HINT2STR(hint)); + + if (err != 0) { + cmn_err(CE_WARN, "%s%d: Failed to add attr [%s]" + " for %s event\n", ddi_driver_name(dip), + ddi_get_instance(dip), DR_HINT, + ESC_DR_AP_STATE_CHANGE); + + goto done; + } + break; + + default: + cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + + goto done; + } + + break; + + /* event sub class: DDI_HP_CN_REQ */ + case DDI_HP_CN_REQ: + ev_subclass = ESC_DR_REQ; + + switch (hint) { + case SE_INVESTIGATE_RES: /* fall through */ + case SE_INCOMING_RES: /* fall through */ + case SE_OUTGOING_RES: /* fall through */ + err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE, + SE_REQ2STR(hint)); + + if (err != 0) { + cmn_err(CE_WARN, + "%s%d: Failed to add attr [%s] for %s \n" + "event", ddi_driver_name(dip), + ddi_get_instance(dip), + DR_REQ_TYPE, ESC_DR_REQ); + + goto done; + } + break; + + default: + cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + + goto done; + } + + break; + + default: + cmn_err(CE_WARN, "%s%d: Unknown Event subclass\n", + ddi_driver_name(dip), ddi_get_instance(dip)); + + goto done; + } + + /* + * Add Hotplug Connection (CN) as attribute (common attribute) + */ + err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap_id); + if (err != 0) { + cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event\n", + ddi_driver_name(dip), ddi_get_instance(dip), + DR_AP_ID, EC_DR); + + goto done; + } + + /* + * Log this event with sysevent framework. + */ + err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_DR, + ev_subclass, ev_attr_list, &eid, + ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP)); + + if (err != 0) { + cmn_err(CE_WARN, "%s%d: Failed to log %s event\n", + ddi_driver_name(dip), ddi_get_instance(dip), EC_DR); + } + +done: + nvlist_free(ev_attr_list); + kmem_free(ap_id, ap_id_len); +} + +/* + * Local functions (called within this file) + */ + +/* + * Connector operations + */ + +/* + * Prepare to change state for a Connector: offline, unprobe, etc. + */ +static int +ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; + dev_info_t *dip = hdlp->cn_dip; + int rv = DDI_SUCCESS; + + if (curr_state > target_state && + curr_state == DDI_HP_CN_STATE_ENABLED) { + /* + * If the Connection goes to a lower state from ENABLED, + * then offline all children under it. + */ + rv = ddihp_cn_change_children_state(hdlp, B_FALSE); + if (rv != DDI_SUCCESS) { + cmn_err(CE_WARN, + "(%s%d): " + "failed to unconfigure the device in the" + " Connection %s\n", ddi_driver_name(dip), + ddi_get_instance(dip), + hdlp->cn_info.cn_name); + + return (rv); + } + ASSERT(NEXUS_HAS_HP_OP(dip)); + /* + * Remove all the children and their ports + * after they are offlined. + */ + rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( + dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_UNPROBE, + NULL, NULL); + if (rv != DDI_SUCCESS) { + cmn_err(CE_WARN, + "(%s%d): failed" + " to unprobe the device in the Connector" + " %s\n", ddi_driver_name(dip), + ddi_get_instance(dip), + hdlp->cn_info.cn_name); + + return (rv); + } + + DDI_HP_NEXDBG((CE_CONT, + "ddihp_connector_ops (%s%d): device" + " is unconfigured and unprobed in Connector %s\n", + ddi_driver_name(dip), ddi_get_instance(dip), + hdlp->cn_info.cn_name)); + } + + return (rv); +} + +/* + * Jobs after change state of a Connector: update last change time, + * probe, online, sysevent, etc. + */ +static int +ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t new_state) +{ + int rv = DDI_SUCCESS; + ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; + + /* Update the state in handle */ + if (new_state != curr_state) { + hdlp->cn_info.cn_state = new_state; + ddihp_update_last_change(hdlp); + } + + if (curr_state < new_state && + new_state == DDI_HP_CN_STATE_ENABLED) { + /* + * Probe and online devices if state is + * upgraded to ENABLED. + */ + rv = ddihp_cn_handle_state_change(hdlp); + } + if (curr_state != hdlp->cn_info.cn_state) { + /* + * For Connector, generate a sysevent on + * state change. + */ + ddihp_cn_gen_sysevent(hdlp, DDI_HP_CN_STATE_CHANGE, + SE_NO_HINT, KM_SLEEP); + } + + return (rv); +} + +/* + * Handle Connector state change. + * + * This function is called after connector is upgraded to ENABLED sate. + * It probes the device plugged in the connector to setup devinfo nodes + * and then online the nodes. + */ +static int +ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp) +{ + dev_info_t *dip = hdlp->cn_dip; + int rv = DDI_SUCCESS; + + ASSERT(DEVI_BUSY_OWNED(dip)); + ASSERT(NEXUS_HAS_HP_OP(dip)); + /* + * If the Connection went to state ENABLED from a lower state, + * probe it. + */ + rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( + dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_PROBE, NULL, NULL); + + if (rv != DDI_SUCCESS) { + ddi_hp_cn_state_t target_state = DDI_HP_CN_STATE_POWERED; + ddi_hp_cn_state_t result_state = 0; + + /* + * Probe failed. Disable the connector so that it can + * be enabled again by a later try from userland. + */ + (void) (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))( + dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_CHANGE_STATE, + (void *)&target_state, (void *)&result_state); + + if (result_state && result_state != hdlp->cn_info.cn_state) { + hdlp->cn_info.cn_state = result_state; + ddihp_update_last_change(hdlp); + } + + cmn_err(CE_WARN, + "(%s%d): failed to probe the Connection %s\n", + ddi_driver_name(dip), ddi_get_instance(dip), + hdlp->cn_info.cn_name); + + return (rv); + } + /* + * Try to online all the children of CN. + */ + (void) ddihp_cn_change_children_state(hdlp, B_TRUE); + + DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_event_handler (%s%d): " + "device is configured in the Connection %s\n", + ddi_driver_name(dip), ddi_get_instance(dip), + hdlp->cn_info.cn_name)); + return (rv); +} + +/* + * Online/Offline all the children under the Hotplug Connection (CN) + * + * Do online operation when the online parameter is true; otherwise do offline. + */ +static int +ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, boolean_t online) +{ + ddi_hp_cn_cfg_t cn_cfg; + dev_info_t *dip = hdlp->cn_dip; + dev_info_t *cdip; + ddi_hp_cn_handle_t *h; + int rv = DDI_SUCCESS; + + DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state:" + " dip %p hdlp %p, online %x\n", + (void *)dip, (void *)hdlp, online)); + + cn_cfg.online = online; + ASSERT(DEVI_BUSY_OWNED(dip)); + + /* + * Return invalid if Connection state is < DDI_HP_CN_STATE_ENABLED + * when try to online children. + */ + if (online && hdlp->cn_info.cn_state < DDI_HP_CN_STATE_ENABLED) { + DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state: " + "Connector %p is not in probed state\n", (void *)hdlp)); + + return (DDI_EINVAL); + } + + /* Now, online/offline all the devices depending on the Connector */ + + if (!online) { + /* + * For offline operation we need to firstly clean up devfs + * so as not to prevent driver detach. + */ + (void) devfs_clean(dip, NULL, DV_CLEAN_FORCE); + } + for (h = DEVI(dip)->devi_hp_hdlp; h; h = h->next) { + if (h->cn_info.cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT) + continue; + + if (h->cn_info.cn_num_dpd_on != + hdlp->cn_info.cn_num) + continue; + + cdip = h->cn_info.cn_child; + ASSERT(cdip); + if (online) { + /* online children */ + if (!ddihp_check_status_prop(dip)) + continue; + + if (ndi_devi_online(cdip, + NDI_ONLINE_ATTACH | NDI_CONFIG) != NDI_SUCCESS) { + cmn_err(CE_WARN, + "(%s%d):" + " failed to attach driver for a device" + " (%s%d) under the Connection %s\n", + ddi_driver_name(dip), ddi_get_instance(dip), + ddi_driver_name(cdip), + ddi_get_instance(cdip), + hdlp->cn_info.cn_name); + /* + * One of the devices failed to online, but we + * want to continue to online the rest siblings + * after mark the failure here. + */ + rv = DDI_FAILURE; + + continue; + } + cn_cfg.rv = NDI_SUCCESS; + if (ddi_get_child(cdip)) { + /* Continue to online grand children */ + int c; + + ndi_devi_enter(cdip, &c); + ddi_walk_devs(ddi_get_child(cdip), + ddihp_change_node_state, + (void *)&cn_cfg); + ndi_devi_exit(cdip, c); + } + if (cn_cfg.rv != NDI_SUCCESS) { + /* + * one of the grand children is not ONLINE'd. + */ + cmn_err(CE_WARN, + "(%s%d):" + " failed to attach driver for a grandchild" + "device (%s%d) in the Connection %s\n", + ddi_driver_name(dip), ddi_get_instance(dip), + ddi_driver_name(cdip), + ddi_get_instance(cdip), + hdlp->cn_info.cn_name); + + rv = DDI_FAILURE; + } + + } else { + /* offline children */ + if (ndi_devi_offline(cdip, NDI_UNCONFIG) != + NDI_SUCCESS) { + cmn_err(CE_WARN, + "(%s%d):" + " failed to dettach driver for the device" + " (%s%d) in the Connection %s\n", + ddi_driver_name(dip), ddi_get_instance(dip), + ddi_driver_name(cdip), + ddi_get_instance(cdip), + hdlp->cn_info.cn_name); + + return (DDI_EBUSY); + } + } + } + + return (rv); +} + +/* + * This function is called to online or offline the dev_info nodes for an + * Hotplug Connection (CN). + */ +static int +ddihp_change_node_state(dev_info_t *dip, void *arg) +{ + ddi_hp_cn_cfg_t *cn_cfg_p = (ddi_hp_cn_cfg_t *)arg; + int rv; + + if (cn_cfg_p->online) { + /* It is online operation */ + if (!ddihp_check_status_prop(dip)) + return (DDI_WALK_PRUNECHILD); + + rv = ndi_devi_online(dip, NDI_ONLINE_ATTACH | NDI_CONFIG); + } else { + /* It is offline operation */ + (void) devfs_clean(ddi_get_parent(dip), NULL, DV_CLEAN_FORCE); + rv = ndi_devi_offline(dip, NDI_UNCONFIG); + } + if (rv != NDI_SUCCESS) { + DDI_HP_IMPLDBG((CE_CONT, "ddihp_change_devinfo_node_state:" + " failed op %x rv %d\n", cn_cfg_p->online, rv)); + cn_cfg_p->rv = rv; + + /* Failed to attach/detach the driver(s) */ + return (DDI_WALK_PRUNECHILD); + } + + /* Continue the walk */ + return (DDI_WALK_CONTINUE); +} + +/* + * Port operations + */ + +/* + * Change Port state to target_state. + */ +static int +ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state; + + if (target_state < DDI_HP_CN_STATE_PORT_EMPTY || + target_state > DDI_HP_CN_STATE_ONLINE) { + + return (DDI_EINVAL); + } + + if (curr_state < target_state) + return (ddihp_port_upgrade_state(hdlp, target_state)); + else if (curr_state > target_state) + return (ddihp_port_downgrade_state(hdlp, target_state)); + else + return (DDI_SUCCESS); +} + +/* + * Upgrade port state to target_state. + */ +static int +ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state, new_state, result_state; + dev_info_t *cdip; + int rv = DDI_SUCCESS; + + curr_state = hdlp->cn_info.cn_state; + while (curr_state < target_state) { + switch (curr_state) { + case DDI_HP_CN_STATE_PORT_EMPTY: + /* Check the existence of the corresponding hardware */ + new_state = DDI_HP_CN_STATE_PORT_PRESENT; + rv = ddihp_connector_ops(hdlp, + DDI_HPOP_CN_CHANGE_STATE, + (void *)&new_state, (void *)&result_state); + if (rv == DDI_SUCCESS) { + hdlp->cn_info.cn_state = + result_state; + } + break; + case DDI_HP_CN_STATE_PORT_PRESENT: + /* Read-only probe the corresponding hardware. */ + new_state = DDI_HP_CN_STATE_OFFLINE; + rv = ddihp_connector_ops(hdlp, + DDI_HPOP_CN_CHANGE_STATE, + (void *)&new_state, &cdip); + if (rv == DDI_SUCCESS) { + hdlp->cn_info.cn_state = + DDI_HP_CN_STATE_OFFLINE; + + ASSERT(hdlp->cn_info.cn_child == NULL); + hdlp->cn_info.cn_child = cdip; + } + break; + case DDI_HP_CN_STATE_OFFLINE: + /* fall through */ + case DDI_HP_CN_STATE_MAINTENANCE: + + cdip = hdlp->cn_info.cn_child; + + rv = ndi_devi_online(cdip, + NDI_ONLINE_ATTACH | NDI_CONFIG); + if (rv == NDI_SUCCESS) { + hdlp->cn_info.cn_state = + DDI_HP_CN_STATE_ONLINE; + rv = DDI_SUCCESS; + } else { + rv = DDI_FAILURE; + DDI_HP_IMPLDBG((CE_CONT, + "ddihp_port_upgrade_state: " + "failed to online device %p at port: %s\n", + (void *)cdip, hdlp->cn_info.cn_name)); + } + break; + case DDI_HP_CN_STATE_ONLINE: + + break; + default: + /* should never reach here */ + ASSERT("unknown devinfo state"); + } + curr_state = hdlp->cn_info.cn_state; + if (rv != DDI_SUCCESS) { + DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_upgrade_state: " + "failed curr_state=%x, target_state=%x \n", + curr_state, target_state)); + return (rv); + } + } + + return (rv); +} + +/* + * Downgrade state to target_state + */ +static int +ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state) +{ + ddi_hp_cn_state_t curr_state, new_state, result_state; + dev_info_t *dip = hdlp->cn_dip; + dev_info_t *cdip; + int rv = DDI_SUCCESS; + + curr_state = hdlp->cn_info.cn_state; + while (curr_state > target_state) { + + switch (curr_state) { + case DDI_HP_CN_STATE_PORT_EMPTY: + + break; + case DDI_HP_CN_STATE_PORT_PRESENT: + /* Check the existence of the corresponding hardware */ + new_state = DDI_HP_CN_STATE_PORT_EMPTY; + rv = ddihp_connector_ops(hdlp, + DDI_HPOP_CN_CHANGE_STATE, + (void *)&new_state, (void *)&result_state); + if (rv == DDI_SUCCESS) + hdlp->cn_info.cn_state = + result_state; + + break; + case DDI_HP_CN_STATE_OFFLINE: + /* + * Read-only unprobe the corresponding hardware: + * 1. release the assigned resource; + * 2. remove the node pointed by the port's cn_child + */ + new_state = DDI_HP_CN_STATE_PORT_PRESENT; + rv = ddihp_connector_ops(hdlp, + DDI_HPOP_CN_CHANGE_STATE, + (void *)&new_state, (void *)&result_state); + if (rv == DDI_SUCCESS) + hdlp->cn_info.cn_state = + DDI_HP_CN_STATE_PORT_PRESENT; + break; + case DDI_HP_CN_STATE_MAINTENANCE: + /* fall through. */ + case DDI_HP_CN_STATE_ONLINE: + cdip = hdlp->cn_info.cn_child; + + (void) devfs_clean(dip, NULL, DV_CLEAN_FORCE); + rv = ndi_devi_offline(cdip, NDI_UNCONFIG); + if (rv == NDI_SUCCESS) { + hdlp->cn_info.cn_state = + DDI_HP_CN_STATE_OFFLINE; + rv = DDI_SUCCESS; + } else { + rv = DDI_EBUSY; + DDI_HP_IMPLDBG((CE_CONT, + "ddihp_port_downgrade_state: failed " + "to offline node, rv=%x, cdip=%p \n", + rv, (void *)cdip)); + } + + break; + default: + /* should never reach here */ + ASSERT("unknown devinfo state"); + } + curr_state = hdlp->cn_info.cn_state; + if (rv != DDI_SUCCESS) { + DDI_HP_IMPLDBG((CE_CONT, + "ddihp_port_downgrade_state: failed " + "curr_state=%x, target_state=%x \n", + curr_state, target_state)); + return (rv); + } + } + + return (rv); +} + +/* + * Misc routines + */ + +/* Update the last state change time */ +static void +ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp) +{ + time_t time; + + if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS) + hdlp->cn_info.cn_last_change = (time_t)-1; + else + hdlp->cn_info.cn_last_change = (time32_t)time; +} + +/* + * Check the device for a 'status' property. A conforming device + * should have a status of "okay", "disabled", "fail", or "fail-xxx". + * + * Return FALSE for a conforming device that is disabled or faulted. + * Return TRUE in every other case. + * + * 'status' property is NOT a bus specific property. It is defined in page 184, + * IEEE 1275 spec. The full name of the spec is "IEEE Standard for + * Boot (Initialization Configuration) Firmware: Core Requirements and + * Practices". + */ +static boolean_t +ddihp_check_status_prop(dev_info_t *dip) +{ + char *status_prop; + boolean_t rv = B_TRUE; + + /* try to get the 'status' property */ + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + "status", &status_prop) == DDI_PROP_SUCCESS) { + /* + * test if the status is "disabled", "fail", or + * "fail-xxx". + */ + if (strcmp(status_prop, "disabled") == 0) { + rv = B_FALSE; + DDI_HP_IMPLDBG((CE_CONT, "ddihp_check_status_prop " + "(%s%d): device is in disabled state", + ddi_driver_name(dip), ddi_get_instance(dip))); + } else if (strncmp(status_prop, "fail", 4) == 0) { + rv = B_FALSE; + cmn_err(CE_WARN, + "hotplug (%s%d): device is in fault state (%s)\n", + ddi_driver_name(dip), ddi_get_instance(dip), + status_prop); + } + + ddi_prop_free(status_prop); + } + + return (rv); +} diff --git a/usr/src/uts/common/os/ddi_hp_ndi.c b/usr/src/uts/common/os/ddi_hp_ndi.c new file mode 100644 index 0000000000..d8cb0de3a2 --- /dev/null +++ b/usr/src/uts/common/os/ddi_hp_ndi.c @@ -0,0 +1,405 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * Sun NDI hotplug interfaces + */ + +#include <sys/note.h> +#include <sys/sysmacros.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kmem.h> +#include <sys/cmn_err.h> +#include <sys/debug.h> +#include <sys/avintr.h> +#include <sys/autoconf.h> +#include <sys/sunndi.h> +#include <sys/ndi_impldefs.h> +#include <sys/ddi.h> +#include <sys/disp.h> +#include <sys/stat.h> +#include <sys/callb.h> +#include <sys/sysevent.h> +#include <sys/sysevent/eventdefs.h> +#include <sys/sysevent/dr.h> +#include <sys/taskq.h> + +/* Local functions prototype */ +static void ddihp_cn_run_event(void *arg); +static int ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state); + +/* + * Global functions (called by hotplug controller or nexus drivers) + */ + +/* + * Register the Hotplug Connection (CN) + */ +int +ndi_hp_register(dev_info_t *dip, ddi_hp_cn_info_t *info_p) +{ + ddi_hp_cn_handle_t *hdlp; + int count; + + DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, info_p %p\n", + (void *)dip, (void *)info_p)); + + ASSERT(!servicing_interrupt()); + if (servicing_interrupt()) + return (NDI_FAILURE); + + /* Validate the arguments */ + if ((dip == NULL) || (info_p == NULL)) + return (NDI_EINVAL); + + if (!NEXUS_HAS_HP_OP(dip)) { + return (NDI_ENOTSUP); + } + /* Lock before access */ + ndi_devi_enter(dip, &count); + + hdlp = ddihp_cn_name_to_handle(dip, info_p->cn_name); + if (hdlp) { + /* This cn_name is already registered. */ + ndi_devi_exit(dip, count); + + return (NDI_SUCCESS); + } + /* + * Create and initialize hotplug Connection handle + */ + hdlp = (ddi_hp_cn_handle_t *)kmem_zalloc( + (sizeof (ddi_hp_cn_handle_t)), KM_SLEEP); + + /* Copy the Connection information */ + hdlp->cn_dip = dip; + bcopy(info_p, &(hdlp->cn_info), sizeof (*info_p)); + + /* Copy cn_name */ + hdlp->cn_info.cn_name = ddi_strdup(info_p->cn_name, KM_SLEEP); + + if (ddihp_cn_getstate(hdlp) != DDI_SUCCESS) { + DDI_HP_NEXDBG((CE_CONT, "ndi_hp_register: dip %p, hdlp %p" + "ddi_cn_getstate failed\n", (void *)dip, (void *)hdlp)); + + goto fail; + } + + /* + * Append the handle to the list + */ + DDIHP_LIST_APPEND(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), + hdlp); + + ndi_devi_exit(dip, count); + + return (NDI_SUCCESS); + +fail: + kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1); + kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t)); + ndi_devi_exit(dip, count); + + return (NDI_FAILURE); +} + +/* + * Unregister a Hotplug Connection (CN) + */ +int +ndi_hp_unregister(dev_info_t *dip, char *cn_name) +{ + ddi_hp_cn_handle_t *hdlp; + int count; + int ret; + + DDI_HP_NEXDBG((CE_CONT, "ndi_hp_unregister: dip %p, cn name %s\n", + (void *)dip, cn_name)); + + ASSERT(!servicing_interrupt()); + if (servicing_interrupt()) + return (NDI_FAILURE); + + /* Validate the arguments */ + if ((dip == NULL) || (cn_name == NULL)) + return (NDI_EINVAL); + + ndi_devi_enter(dip, &count); + + hdlp = ddihp_cn_name_to_handle(dip, cn_name); + if (hdlp == NULL) { + ndi_devi_exit(dip, count); + return (NDI_EINVAL); + } + + switch (ddihp_cn_unregister(hdlp)) { + case DDI_SUCCESS: + ret = NDI_SUCCESS; + break; + case DDI_EINVAL: + ret = NDI_EINVAL; + break; + case DDI_EBUSY: + ret = NDI_BUSY; + break; + default: + ret = NDI_FAILURE; + break; + } + + ndi_devi_exit(dip, count); + + return (ret); +} + +/* + * Notify the Hotplug Connection (CN) to change state. + * Flag: + * DDI_HP_REQ_SYNC Return after the change is finished. + * DDI_HP_REQ_ASYNC Return after the request is dispatched. + */ +int +ndi_hp_state_change_req(dev_info_t *dip, char *cn_name, + ddi_hp_cn_state_t state, uint_t flag) +{ + ddi_hp_cn_async_event_entry_t *eventp; + + DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: dip %p " + "cn_name: %s, state %x, flag %x\n", + (void *)dip, cn_name, state, flag)); + + /* Validate the arguments */ + if (dip == NULL || cn_name == NULL) + return (NDI_EINVAL); + + if (!NEXUS_HAS_HP_OP(dip)) { + return (NDI_ENOTSUP); + } + /* + * If the request is to handle the event synchronously, then call + * the event handler without queuing the event. + */ + if (flag & DDI_HP_REQ_SYNC) { + ddi_hp_cn_handle_t *hdlp; + int count; + int ret; + + ASSERT(!servicing_interrupt()); + if (servicing_interrupt()) + return (NDI_FAILURE); + + ndi_devi_enter(dip, &count); + + hdlp = ddihp_cn_name_to_handle(dip, cn_name); + if (hdlp == NULL) { + ndi_devi_exit(dip, count); + + return (NDI_EINVAL); + } + + DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: hdlp %p " + "calling ddihp_cn_req_handler() directly to handle " + "target_state %x\n", (void *)hdlp, state)); + + ret = ddihp_cn_req_handler(hdlp, state); + + ndi_devi_exit(dip, count); + + return (ret); + } + + eventp = kmem_zalloc(sizeof (ddi_hp_cn_async_event_entry_t), + KM_NOSLEEP); + if (eventp == NULL) + return (NDI_NOMEM); + + eventp->cn_name = ddi_strdup(cn_name, KM_NOSLEEP); + if (eventp->cn_name == NULL) { + kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t)); + return (NDI_NOMEM); + } + eventp->dip = dip; + eventp->target_state = state; + + /* + * Hold the parent's ref so that it won't disappear when the taskq is + * scheduled to run. + */ + ndi_hold_devi(dip); + + if (!taskq_dispatch(system_taskq, ddihp_cn_run_event, eventp, + TQ_NOSLEEP)) { + ndi_rele_devi(dip); + DDI_HP_NEXDBG((CE_CONT, "ndi_hp_state_change_req: " + "taskq_dispatch failed! dip %p " + "target_state %x\n", (void *)dip, state)); + return (NDI_NOMEM); + } + + return (NDI_CLAIMED); +} + +/* + * Walk the link of Hotplug Connection handles of a dip: + * DEVI(dip)->devi_hp_hdlp->[link of connections] + */ +void +ndi_hp_walk_cn(dev_info_t *dip, int (*f)(ddi_hp_cn_info_t *, + void *), void *arg) +{ + int count; + ddi_hp_cn_handle_t *head, *curr, *prev; + + DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p arg %p\n", + (void *)dip, arg)); + + ASSERT(!servicing_interrupt()); + if (servicing_interrupt()) + return; + + /* Validate the arguments */ + if (dip == NULL) + return; + + ndi_devi_enter(dip, &count); + + head = DEVI(dip)->devi_hp_hdlp; + curr = head; + prev = NULL; + while (curr != NULL) { + DDI_HP_NEXDBG((CE_CONT, "ndi_hp_walk_cn: dip %p " + "current cn_name: %s\n", + (void *)dip, curr->cn_info.cn_name)); + switch ((*f)(&(curr->cn_info), arg)) { + case DDI_WALK_TERMINATE: + ndi_devi_exit(dip, count); + + return; + case DDI_WALK_CONTINUE: + default: + if (DEVI(dip)->devi_hp_hdlp != head) { + /* + * The current node is head and it is removed + * by last call to (*f)() + */ + head = DEVI(dip)->devi_hp_hdlp; + curr = head; + prev = NULL; + } else if (prev && prev->next != curr) { + /* + * The current node is a middle node or tail + * node and it is removed by last call to + * (*f)() + */ + curr = prev->next; + } else { + /* no removal accurred on curr node */ + prev = curr; + curr = curr->next; + } + } + } + ndi_devi_exit(dip, count); +} + +/* + * Local functions (called within this file) + */ + +/* + * Wrapper function for ddihp_cn_req_handler() called from taskq + */ +static void +ddihp_cn_run_event(void *arg) +{ + ddi_hp_cn_async_event_entry_t *eventp = + (ddi_hp_cn_async_event_entry_t *)arg; + dev_info_t *dip = eventp->dip; + ddi_hp_cn_handle_t *hdlp; + int count; + + /* Lock before access */ + ndi_devi_enter(dip, &count); + + hdlp = ddihp_cn_name_to_handle(dip, eventp->cn_name); + if (hdlp) { + (void) ddihp_cn_req_handler(hdlp, eventp->target_state); + } else { + DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_run_event: no handle for " + "cn_name: %s dip %p. Request for target_state %x is" + " dropped. \n", + eventp->cn_name, (void *)dip, eventp->target_state)); + } + + ndi_devi_exit(dip, count); + + /* Release the devi's ref that is held from interrupt context. */ + ndi_rele_devi((dev_info_t *)DEVI(dip)); + kmem_free(eventp->cn_name, strlen(eventp->cn_name) + 1); + kmem_free(eventp, sizeof (ddi_hp_cn_async_event_entry_t)); +} + +/* + * Handle state change request of a Hotplug Connection (CN) + */ +static int +ddihp_cn_req_handler(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_state_t target_state) +{ + dev_info_t *dip = hdlp->cn_dip; + int ret = DDI_SUCCESS; + + DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler:" + " hdlp %p, target_state %x\n", + (void *)hdlp, target_state)); + + ASSERT(DEVI_BUSY_OWNED(dip)); + + if (ddihp_cn_getstate(hdlp) != DDI_SUCCESS) { + DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, " + "hdlp %p ddi_cn_getstate failed\n", (void *)dip, + (void *)hdlp)); + + return (NDI_UNCLAIMED); + } + if (hdlp->cn_info.cn_state != target_state) { + ddi_hp_cn_state_t result_state = 0; + + DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_CHANGE_STATE, + (void *)&target_state, (void *)&result_state, ret); + + DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_req_handler: dip %p, " + "hdlp %p changed state to %x, ret=%x\n", + (void *)dip, (void *)hdlp, result_state, ret)); + } + + if (ret == DDI_SUCCESS) + return (NDI_CLAIMED); + else + return (NDI_UNCLAIMED); +} diff --git a/usr/src/uts/common/os/devcfg.c b/usr/src/uts/common/os/devcfg.c index 871b1c4c91..e36f63bd4c 100644 --- a/usr/src/uts/common/os/devcfg.c +++ b/usr/src/uts/common/os/devcfg.c @@ -407,6 +407,7 @@ i_ddi_free_node(dev_info_t *dip) ASSERT(devi->devi_addr == NULL); ASSERT(devi->devi_node_state == DS_PROTO); ASSERT(devi->devi_child == NULL); + ASSERT(devi->devi_hp_hdlp == NULL); #if defined(__x86) && !defined(__xpv) for (gfxp = gfx_devinfo_list; gfxp; gfxp = gfxp->g_next) { @@ -652,7 +653,7 @@ link_node(dev_info_t *dip) * This is a temporary workaround for Bug 4618861. * We keep the scsi_vhci nexus node on the left side of the devinfo * tree (under the root nexus driver), so that virtual nodes under - * scsi_vhci will be SUSPENDed first and RESUMEd last. This ensures + * scsi_vhci will be SUSPENDed first and RESUMEd last. This ensures * that the pHCI nodes are active during times when their clients * may be depending on them. This workaround embodies the knowledge * that system PM and CPR both traverse the tree left-to-right during @@ -695,6 +696,7 @@ unlink_node(dev_info_t *dip) struct dev_info *devi = DEVI(dip); struct dev_info *parent = devi->devi_parent; dev_info_t **dipp; + ddi_hp_cn_handle_t *hdlp; ASSERT(parent != NULL); ASSERT(devi->devi_node_state == DS_LINKED); @@ -737,6 +739,11 @@ unlink_node(dev_info_t *dip) remove_from_dn_list(&orphanlist, dip); } + /* Update parent's hotplug handle list */ + for (hdlp = DEVI(parent)->devi_hp_hdlp; hdlp; hdlp = hdlp->next) { + if (hdlp->cn_info.cn_child == dip) + hdlp->cn_info.cn_child = NULL; + } return (DDI_SUCCESS); } @@ -2453,7 +2460,7 @@ i_ddi_get_exported_classes(dev_info_t *dip, char ***classes) n += get_class(ddi_driver_name(dip), buf); unlock_hw_class_list(); - ASSERT(n == nclass); /* make sure buf wasn't overrun */ + ASSERT(n == nclass); /* make sure buf wasn't overrun */ return (nclass); } @@ -3094,7 +3101,7 @@ debug_dtree(dev_info_t *devi, struct dev_info *adevi, char *service) } #else /* DEBUG */ #define debug_dtree(a1, a2, a3) /* nothing */ -#endif /* DEBUG */ +#endif /* DEBUG */ static void ddi_optimize_dtree(dev_info_t *devi) @@ -3288,7 +3295,7 @@ i_ddi_forceattach_drivers() * when uhci/ohci reset themselves, it induces a port change on * the ehci companion controller. Since there's no interrupt handler * installed at the time, the moment that interrupt is unmasked, an - * interrupt storm will occur. All this is averted when ehci is + * interrupt storm will occur. All this is averted when ehci is * loaded first. And now you know..... the REST of the story. * * Regardless of platform, ehci needs to initialize first to avoid @@ -4226,7 +4233,7 @@ reset_leaves(void) * outstanding attach or detach operations in progress when quiesce_devices() or * reset_leaves()is invoked. It must be called before the system becomes * single-threaded because device attach and detach are multi-threaded - * operations. (note that during system shutdown the system doesn't actually + * operations. (note that during system shutdown the system doesn't actually * become single-thread since other threads still exist, but the shutdown thread * will disable preemption for itself, raise it's pil, and stop all the other * cpus in the system there by effectively making the system single-threaded.) @@ -4420,7 +4427,7 @@ unbind_children_by_driver(dev_info_t *dip, void *arg) * We are called either from rem_drv or update_drv when reloading * a driver.conf file. In either case, we unbind persistent nodes * and destroy .conf nodes. In the case of rem_drv, this will be - * the final state. In the case of update_drv, i_ddi_bind_devs() + * the final state. In the case of update_drv, i_ddi_bind_devs() * may be invoked later to re-enumerate (new) driver.conf rebind * persistent nodes. */ @@ -5371,7 +5378,7 @@ devi_config_one(dev_info_t *pdip, char *devnm, dev_info_t **cdipp, * We may have a genericname on a system that creates drivername * nodes (from .conf files). Find the drivername by nodeid. If we * can't find a node with devnm as the node name then we search by - * drivername. This allows an implementation to supply a genericly + * drivername. This allows an implementation to supply a genericly * named boot path (disk) and locate drivename nodes (sd). The * NDI_PROMNAME flag does not apply to /devices/pseudo paths. */ @@ -8480,7 +8487,7 @@ e_ddi_unretire_device(char *path) static int mark_and_fence(dev_info_t *dip, void *arg) { - char *fencepath = (char *)arg; + char *fencepath = (char *)arg; /* * We have already decided to retire this device. The various @@ -8541,7 +8548,7 @@ i_ddi_check_retire(dev_info_t *dip) (void *)dip, path)); /* - * Check if this device is in the "retired" store i.e. should + * Check if this device is in the "retired" store i.e. should * be retired. If not, we have nothing to do. */ if (e_ddi_device_retired(path) == 0) { diff --git a/usr/src/uts/common/os/modctl.c b/usr/src/uts/common/os/modctl.c index 8ec4bc3f7d..1ddec74d87 100644 --- a/usr/src/uts/common/os/modctl.c +++ b/usr/src/uts/common/os/modctl.c @@ -174,7 +174,7 @@ mod_setup(void) num_devs = read_binding_file(majbind, mb_hashtab, make_mbind); /* * Since read_binding_file is common code, it doesn't enforce that all - * of the binding file entries have major numbers <= MAXMAJ32. Thus, + * of the binding file entries have major numbers <= MAXMAJ32. Thus, * ensure that we don't allocate some massive amount of space due to a * bad entry. We can't have major numbers bigger than MAXMAJ32 * until file system support for larger major numbers exists. @@ -1232,7 +1232,7 @@ modctl_devid2paths(ddi_devid_t udevid, char *uminor_name, uint_t flag, char *minor_name = NULL; dev_info_t *dip = NULL; int circ; - struct ddi_minor_data *dmdp; + struct ddi_minor_data *dmdp; char *path = NULL; int ulens; int lens; @@ -1998,7 +1998,7 @@ modctl_remdrv_cleanup(const char *u_drvname) * instance of a device bound to the driver being * removed, remove any underlying devfs attribute nodes. * - * This is a two-step process. First we go through + * This is a two-step process. First we go through * the instance data itself, constructing a list of * the nodes discovered. The second step is then * to find and remove any devfs attribute nodes @@ -2261,6 +2261,67 @@ err: return (ret); } +static int +modctl_hp(int subcmd, const char *path, char *cn_name, uintptr_t arg, + uintptr_t rval) +{ + int error = 0; + size_t pathsz, namesz; + char *devpath, *cn_name_str; + + if (path == NULL) + return (EINVAL); + + devpath = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + error = copyinstr(path, devpath, MAXPATHLEN, &pathsz); + if (error != 0) { + kmem_free(devpath, MAXPATHLEN); + return (EFAULT); + } + + cn_name_str = kmem_zalloc(MAXNAMELEN, KM_SLEEP); + error = copyinstr(cn_name, cn_name_str, MAXNAMELEN, &namesz); + if (error != 0) { + kmem_free(devpath, MAXPATHLEN); + kmem_free(cn_name_str, MAXNAMELEN); + + return (EFAULT); + } + + switch (subcmd) { + case MODHPOPS_CHANGE_STATE: + error = ddihp_modctl(DDI_HPOP_CN_CHANGE_STATE, devpath, + cn_name_str, arg, NULL); + break; + case MODHPOPS_CREATE_PORT: + /* Create an empty PORT */ + error = ddihp_modctl(DDI_HPOP_CN_CREATE_PORT, devpath, + cn_name_str, NULL, NULL); + break; + case MODHPOPS_REMOVE_PORT: + /* Remove an empty PORT */ + error = ddihp_modctl(DDI_HPOP_CN_REMOVE_PORT, devpath, + cn_name_str, NULL, NULL); + break; + case MODHPOPS_BUS_GET: + error = ddihp_modctl(DDI_HPOP_CN_GET_PROPERTY, devpath, + cn_name_str, arg, rval); + break; + case MODHPOPS_BUS_SET: + error = ddihp_modctl(DDI_HPOP_CN_SET_PROPERTY, devpath, + cn_name_str, arg, rval); + break; + default: + error = ENOTSUP; + break; + } + + kmem_free(devpath, MAXPATHLEN); + kmem_free(cn_name_str, MAXNAMELEN); + + return (error); +} + int modctl_moddevname(int subcmd, uintptr_t a1, uintptr_t a2) { @@ -2421,7 +2482,7 @@ modctl(int cmd, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, #endif break; - case MODGETDEVFSPATH: /* get path name of (dev_t,spec) type */ + case MODGETDEVFSPATH: /* get path name of (dev_t,spec) type */ if (get_udatamodel() == DATAMODEL_NATIVE) { error = modctl_devfspath((dev_t)a1, (int)a2, (uint_t)a3, (char *)a4); @@ -2439,7 +2500,7 @@ modctl(int cmd, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, (uint_t *)a3); break; - case MODGETDEVFSPATH_MI: /* get path name of (major,instance) */ + case MODGETDEVFSPATH_MI: /* get path name of (major,instance) */ error = modctl_devfspath_mi((major_t)a1, (int)a2, (uint_t)a3, (char *)a4); break; @@ -2536,6 +2597,11 @@ modctl(int cmd, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, error = modctl_unretire((char *)a1); break; + case MODHPOPS: /* hotplug operations */ + /* device named by physpath a2 and Connection name a3 */ + error = modctl_hp((int)a1, (char *)a2, (char *)a3, a4, a5); + break; + default: error = EINVAL; break; @@ -3235,7 +3301,7 @@ modgetsymname(uintptr_t value, ulong_t *offset) /* * Lookup a symbol in a specified module. These are wrapper routines that - * call kobj_lookup(). kobj_lookup() may go away but these wrappers will + * call kobj_lookup(). kobj_lookup() may go away but these wrappers will * prevent callers from noticing. */ uintptr_t @@ -4171,7 +4237,7 @@ mod_make_requisite(struct modctl *dependent, struct modctl *on_mod) * which are dependent on it from being uninstalled and * unloaded. "on_mod"'s mod_ref count decremented in * mod_release_requisites when the "dependent" module - * unload is complete. "on_mod" must be loaded, but may not + * unload is complete. "on_mod" must be loaded, but may not * yet be installed. */ on_mod->mod_ref++; diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile index 83a852b35b..c8be2f37c4 100644 --- a/usr/src/uts/common/sys/Makefile +++ b/usr/src/uts/common/sys/Makefile @@ -148,6 +148,8 @@ CHKHDRS= \ ddi.h \ ddifm.h \ ddifm_impl.h \ + ddi_hp.h \ + ddi_hp_impl.h \ ddi_intr.h \ ddi_intr_impl.h \ ddi_impldefs.h \ diff --git a/usr/src/uts/common/sys/autoconf.h b/usr/src/uts/common/sys/autoconf.h index 0f83c24f0b..acf41f6155 100644 --- a/usr/src/uts/common/sys/autoconf.h +++ b/usr/src/uts/common/sys/autoconf.h @@ -26,7 +26,6 @@ #ifndef _SYS_AUTOCONF_H #define _SYS_AUTOCONF_H - /* Derived from autoconf.h, SunOS 4.1.1 1.15 */ #ifdef __cplusplus @@ -113,6 +112,9 @@ struct devnames { #define LDI_EV_DEBUG 0x8000 /* LDI events debug messages */ #define LDI_EV_TRACE 0x10000 /* LDI events trace messages */ #define DDI_INTR_IRM 0x20000 /* interrupt resource management */ +#define DDI_HP_API 0x40000 /* Hotplug interface messages */ +#define DDI_HP_IMPL 0x80000 /* Hotplug implementation msgs */ +#define DDI_HP_NEXUS 0x100000 /* Hotplug messages from nexuses */ extern int ddidebug; @@ -133,6 +135,9 @@ extern int ddidebug; #define LDI_EVDBG(args) if (ddidebug & LDI_EV_DEBUG) cmn_err args #define LDI_EVTRC(args) if (ddidebug & LDI_EV_TRACE) cmn_err args #define DDI_INTR_IRMDBG(args) if (ddidebug & DDI_INTR_IRM) cmn_err args +#define DDI_HP_APIDBG(args) if (ddidebug & DDI_HP_API) cmn_err args +#define DDI_HP_IMPLDBG(args) if (ddidebug & DDI_HP_IMPL) cmn_err args +#define DDI_HP_NEXDBG(args) if (ddidebug & DDI_HP_NEXUS) cmn_err args #else #define NDI_CONFIG_DEBUG(args) #define BMDPRINTF(args) @@ -150,6 +155,9 @@ extern int ddidebug; #define LDI_EVDBG(args) if (ddidebug & LDI_EV_DEBUG) cmn_err args #define LDI_EVTRC(args) if (ddidebug & LDI_EV_TRACE) cmn_err args #define DDI_INTR_IRMDBG(args) +#define DDI_HP_APIDBG(args) +#define DDI_HP_IMPLDBG(args) +#define DDI_HP_NEXDBG(args) #endif diff --git a/usr/src/uts/common/sys/ddi_hp.h b/usr/src/uts/common/sys/ddi_hp.h new file mode 100644 index 0000000000..eadb88ed49 --- /dev/null +++ b/usr/src/uts/common/sys/ddi_hp.h @@ -0,0 +1,126 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_DDI_HP_H +#define _SYS_DDI_HP_H + +/* + * Sun DDI hotplug support definitions + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ddi_hp_cn_state_t + * + * Typedef of generic hotplug state machine for Hotplug Connection (CN) + */ +typedef enum { + DDI_HP_CN_STATE_EMPTY = 0x1000, /* Empty */ + DDI_HP_CN_STATE_PRESENT = 0x2000, /* A Device Present */ + DDI_HP_CN_STATE_POWERED = 0x3000, /* Powered */ + DDI_HP_CN_STATE_ENABLED = 0x4000, /* Enabled */ + DDI_HP_CN_STATE_PORT_EMPTY = 0x5000, /* PORT Empty */ + DDI_HP_CN_STATE_PORT_PRESENT = 0x6000, /* A Device Node Present */ + DDI_HP_CN_STATE_OFFLINE = 0x7000, /* Driver not attached */ + DDI_HP_CN_STATE_ATTACHED = 0x8000, /* Device driver attached */ + DDI_HP_CN_STATE_MAINTENANCE = 0x9000, /* Device in maintenance */ + DDI_HP_CN_STATE_ONLINE = 0xa000 /* Device is ready */ +} ddi_hp_cn_state_t; + +/* + * ddi_hp_cn_type_t + * + * Typedef for Hotplug Connection (CN) types. + */ +typedef enum { + DDI_HP_CN_TYPE_VIRTUAL_PORT = 0x1, /* Virtual Hotplug Port */ + DDI_HP_CN_TYPE_PCI = 0x2, /* PCI bus slot */ + DDI_HP_CN_TYPE_PCIE = 0x3 /* PCI Express slot */ +} ddi_hp_cn_type_t; + +#define DDI_HP_CN_TYPE_STR_PORT "Virtual-Port" +/* + * The value set to ddi_hp_cn_info_t->cn_num_dpd_on in the case of the + * connection does not depend on any other connections. + */ +#define DDI_HP_CN_NUM_NONE -1 + +/* + * ddi_hp_cn_info_t + * + * Hotplug Connection (CN) information structure + */ +typedef struct ddi_hp_cn_info { + char *cn_name; /* Name of the Connection */ + /* + * Connection number. + */ + int cn_num; + /* + * Depend-on connection number; + * The connection number on which this connection is depending on. + * If this connection does not depend on any other connections + * under the same parent node, then it's cn_num_dpd_on is set to + * DDI_HP_CN_NUM_NONE. + */ + int cn_num_dpd_on; + + ddi_hp_cn_type_t cn_type; /* Type: Port, PCI, PCIE, ... */ + + /* + * Description string for types of Connection. Set by bus software + * and read by users only. + */ + char *cn_type_str; + /* + * The child device of this Port. + * It is NULL if this is a Connector. + */ + dev_info_t *cn_child; + + ddi_hp_cn_state_t cn_state; /* Hotplug Connection state */ + time32_t cn_last_change; /* Last time state changed. */ +} ddi_hp_cn_info_t; + +typedef struct ddi_hp_property { + char *nvlist_buf; + size_t buf_size; +} ddi_hp_property_t; + +#if defined(_SYSCALL32) +typedef struct ddi_hp_property32 { + caddr32_t nvlist_buf; + uint32_t buf_size; +} ddi_hp_property32_t; +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_DDI_HP_H */ diff --git a/usr/src/uts/common/sys/ddi_hp_impl.h b/usr/src/uts/common/sys/ddi_hp_impl.h new file mode 100644 index 0000000000..9b5a193313 --- /dev/null +++ b/usr/src/uts/common/sys/ddi_hp_impl.h @@ -0,0 +1,160 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_DDI_HP_IMPL_H +#define _SYS_DDI_HP_IMPL_H + +/* + * Sun DDI hotplug implementation specific definitions + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _KERNEL + +/* Flags for sync request and async hotplug request */ +#define DDI_HP_REQ_SYNC 0x0001 +#define DDI_HP_REQ_ASYNC 0x0002 + +/* Check if a handle represents a port or a connector */ +#define DDI_HP_IS_VIRTUAL_PORT(hdlp) \ + (hdlp->cn_info.cn_type == DDI_HP_CN_TYPE_VIRTUAL_PORT) + +/* + * ddi_hp_cn_handle_t + * + * DDI handle for a registered Hotplug Connection (CN) + */ +typedef struct ddi_hp_cn_handle { + dev_info_t *cn_dip; /* The dip that the handle is linked */ + ddi_hp_cn_info_t cn_info; /* Connection info */ + struct ddi_hp_cn_handle *next; /* Next Connector/Port. */ +} ddi_hp_cn_handle_t; + +typedef struct ddi_hp_cn_async_event_entry { + dev_info_t *dip; + char *cn_name; + ddi_hp_cn_state_t target_state; +} ddi_hp_cn_async_event_entry_t; + +/* + * ddi_hp_op_t + * + * Typedef for Hotplug OPS commands used with bus_hp_op() + */ +typedef enum { + DDI_HPOP_CN_GET_STATE = 1, /* Get Connection state */ + DDI_HPOP_CN_CHANGE_STATE, /* Change Connection state */ + DDI_HPOP_CN_PROBE, /* Probe Connection */ + DDI_HPOP_CN_UNPROBE, /* Unprobe Connection */ + DDI_HPOP_CN_GET_PROPERTY, /* Get bus specific property */ + DDI_HPOP_CN_SET_PROPERTY, /* Set bus specific property */ + DDI_HPOP_CN_CREATE_PORT, /* Create a port for virtual hotplug */ + DDI_HPOP_CN_REMOVE_PORT /* Remove an empty port */ +} ddi_hp_op_t; + +#define DDIHP_CN_OPS(hdlp, op, arg, result, ret) \ + if (DDI_HP_IS_VIRTUAL_PORT(hdlp)) \ + ret = ddihp_port_ops(hdlp, op, arg, result); \ + else \ + ret = ddihp_connector_ops(hdlp, op, arg, result); + +#define NEXUS_HAS_HP_OP(dip) \ + ((DEVI(dip)->devi_ops->devo_bus_ops) && \ + (DEVI(dip)->devi_ops->devo_bus_ops->busops_rev >= BUSO_REV_10) && \ + (DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op)) + +/* + * ddi_hp_cn_sysevent_t + * + * The following correspond to sysevent defined subclasses + */ +typedef enum { + DDI_HP_CN_STATE_CHANGE, + DDI_HP_CN_REQ +} ddi_hp_cn_sysevent_t; + +/* + * Control structure for tree walk during configure/unconfigure operation. + */ +typedef struct ddi_hp_cn_cfg { + boolean_t online; /* TRUE for online children; */ + /* FALSE for offline children. */ + int rv; /* Return error code */ +} ddi_hp_cn_cfg_t; + +/* + * Misc + */ + +/* Append a node to list */ +#define DDIHP_LIST_APPEND(type, head, node) \ +if (node) { \ + type *curr, *prev = NULL; \ + (node)->next = NULL; \ + for (curr = (head); curr; prev = curr, curr = curr->next); \ + if (prev == NULL) \ + (head) = (node); \ + else \ + prev->next = (node); \ +} + +/* Remove a node from a list */ +#define DDIHP_LIST_REMOVE(type, head, node) \ +if (node) { \ + type *curr, *prev = NULL; \ + for (curr = (head); curr; prev = curr, curr = curr->next) { \ + if (curr == (node)) \ + break; \ + } \ + if (curr) { \ + if (prev == NULL) \ + (head) = (head)->next; \ + else \ + prev->next = curr->next; \ + } \ +} + +int ddihp_modctl(int hp_op, char *path, char *cn_name, uintptr_t arg, + uintptr_t rval); +ddi_hp_cn_handle_t *ddihp_cn_name_to_handle(dev_info_t *dip, char *cn_name); +int ddihp_cn_getstate(ddi_hp_cn_handle_t *hdlp); +int ddihp_port_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op, + void *arg, void *result); +int ddihp_connector_ops(ddi_hp_cn_handle_t *hdlp, + ddi_hp_op_t op, void *arg, void *result); +void ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t *hdlp, + ddi_hp_cn_sysevent_t event_sub_class, int hint, int kmflag); +int ddihp_cn_unregister(ddi_hp_cn_handle_t *hdlp); + +#endif /* _KERNEL */ + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_DDI_HP_IMPL_H */ diff --git a/usr/src/uts/common/sys/ddi_impldefs.h b/usr/src/uts/common/sys/ddi_impldefs.h index 5b5d103e08..388ecde590 100644 --- a/usr/src/uts/common/sys/ddi_impldefs.h +++ b/usr/src/uts/common/sys/ddi_impldefs.h @@ -39,6 +39,8 @@ #include <sys/epm.h> #include <sys/ddidmareq.h> #include <sys/ddi_intr.h> +#include <sys/ddi_hp.h> +#include <sys/ddi_hp_impl.h> #include <sys/ddi_isa.h> #include <sys/id_space.h> #include <sys/modhash.h> @@ -69,12 +71,12 @@ typedef enum { * Definitions for generic callback mechanism. */ typedef enum { - DDI_CB_INTR_ADD, - DDI_CB_INTR_REMOVE + DDI_CB_INTR_ADD, /* More available interrupts */ + DDI_CB_INTR_REMOVE /* Fewer available interrupts */ } ddi_cb_action_t; typedef enum { - DDI_CB_FLAG_INTR = 0x1 + DDI_CB_FLAG_INTR = 0x1 /* Driver is IRM aware */ } ddi_cb_flags_t; #define DDI_CB_FLAG_VALID(f) ((f) & DDI_CB_FLAG_INTR) @@ -119,6 +121,7 @@ typedef struct devi_bus_priv { struct iommulib_unit; typedef struct iommulib_unit *iommulib_handle_t; typedef uint8_t ndi_flavor_t; +struct ddi_hp_cn_handle; struct dev_info { @@ -231,8 +234,8 @@ struct dev_info { uint_t devi_cpr_flags; - /* For interrupt support */ - devinfo_intr_t *devi_intr_p; + /* Owned by DDI interrupt framework */ + devinfo_intr_t *devi_intr_p; void *devi_nex_pm; /* nexus PM private */ @@ -267,6 +270,9 @@ struct dev_info { ndi_flavor_t devi_flavor; /* flavor assigned by parent */ ndi_flavor_t devi_flavorv_n; /* number of child-flavors */ void **devi_flavorv; /* child-flavor specific data */ + + /* Owned by hotplug framework */ + struct ddi_hp_cn_handle *devi_hp_hdlp; /* hotplug handle list */ }; #define DEVI(dev_info_type) ((struct dev_info *)(dev_info_type)) diff --git a/usr/src/uts/common/sys/devinfo_impl.h b/usr/src/uts/common/sys/devinfo_impl.h index 4f874ea1e3..411ec3f5e2 100644 --- a/usr/src/uts/common/sys/devinfo_impl.h +++ b/usr/src/uts/common/sys/devinfo_impl.h @@ -64,6 +64,9 @@ extern "C" { /* new public flag for the layered drivers framework */ #define DINFOLYR (DIIOC | 0x40) /* get device layering information */ +/* new public flag for the hotplug framework */ +#define DINFOHP (DIIOC | 0x400000) /* include hotplug information */ + /* * Straight ioctl commands, not bitwise operation */ @@ -119,6 +122,7 @@ extern "C" { #define DI_LINK(addr) ((struct di_link *)((void *)(addr))) #define DI_LNODE(addr) ((struct di_lnode *)((void *)(addr))) #define DI_PRIV_FORMAT(addr) ((struct di_priv_format *)((void *)(addr))) +#define DI_HP(addr) ((struct di_hp *)((void *)(addr))) /* * multipath component definitions: Follows the registered component of @@ -269,12 +273,15 @@ struct di_node { /* useful info to export for each tree node */ di_off_t top_phci; di_off_t next_phci; uint32_t multipath_component; /* stores MDI_COMPONENT_* value. */ - /* * devi_flags field */ uint32_t flags; uint32_t di_pad2; /* 4 byte padding for 32bit x86 app. */ + /* + * offset to hotplug nodes. + */ + di_off_t hp_data; }; /* @@ -321,6 +328,22 @@ struct di_path { }; /* + * chain of hotplug information structures + */ +struct di_hp { + di_off_t self; /* make it self addressable */ + di_off_t next; /* next one in chain */ + di_off_t hp_name; /* name of hotplug connection */ + int hp_connection; /* connection number */ + int hp_depends_on; /* connection number depended upon */ + int hp_state; /* current hotplug state */ + int hp_type; /* connection type: PCI, ... */ + di_off_t hp_type_str; /* description of connection type */ + uint32_t hp_last_change; /* timestamp of last change */ + di_off_t hp_child; /* child device node */ +}; + +/* * Flags for snap_state */ #define DI_PATH_SNAP_NOCLIENT 0x01 /* client endpt not in snapshot */ diff --git a/usr/src/uts/common/sys/devops.h b/usr/src/uts/common/sys/devops.h index 705a8dba02..6efcdcc257 100644 --- a/usr/src/uts/common/sys/devops.h +++ b/usr/src/uts/common/sys/devops.h @@ -19,14 +19,13 @@ * CDDL HEADER END */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYS_DEVOPS_H #define _SYS_DEVOPS_H - #include <sys/types.h> #include <sys/cred.h> #include <sys/uio.h> @@ -42,6 +41,8 @@ #include <sys/ddifm.h> #include <sys/nexusdefs.h> #include <sys/ddi_intr.h> +#include <sys/ddi_hp.h> +#include <sys/ddi_hp_impl.h> #include <sys/aio_req.h> #include <vm/page.h> @@ -145,7 +146,8 @@ struct cb_ops { #define BUSO_REV_7 7 #define BUSO_REV_8 8 #define BUSO_REV_9 9 -#define BUSO_REV BUSO_REV_9 +#define BUSO_REV_10 10 +#define BUSO_REV BUSO_REV_10 struct bus_ops { @@ -268,6 +270,13 @@ struct bus_ops { int (*bus_intr_op)(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t op, ddi_intr_handle_impl_t *hdlp, void *result); + + /* + * NOTE: the following busop entrypoint is available with version + * 10 or greater. + */ + int (*bus_hp_op)(dev_info_t *dip, char *cn_name, + ddi_hp_op_t op, void *arg, void *result); }; /* diff --git a/usr/src/uts/common/sys/hotplug/pci/pcicfg.h b/usr/src/uts/common/sys/hotplug/pci/pcicfg.h index c800f90e5a..943ece4875 100644 --- a/usr/src/uts/common/sys/hotplug/pci/pcicfg.h +++ b/usr/src/uts/common/sys/hotplug/pci/pcicfg.h @@ -20,28 +20,35 @@ */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYS_HOTPLUG_PCI_PCICFG_H #define _SYS_HOTPLUG_PCI_PCICFG_H -#pragma ident "%Z%%M% %I% %E% SMI" - #ifdef __cplusplus extern "C" { #endif +typedef enum pcicfg_flags { + /* No probing; used in case of virtual hotplug */ + PCICFG_FLAG_READ_ONLY = 0x1, + /* Enable ARI; used in case of boot case */ + PCICFG_FLAG_ENABLE_ARI = 0x2 +} pcicfg_flags_t; + /* * Interfaces exported by PCI configurator module, kernel/misc/pcicfg. */ -int pcicfg_configure(dev_info_t *, uint_t); -int pcicfg_unconfigure(dev_info_t *, uint_t); +int pcicfg_configure(dev_info_t *, uint_t, uint_t, pcicfg_flags_t); +int pcicfg_unconfigure(dev_info_t *, uint_t, uint_t, pcicfg_flags_t); #define PCICFG_SUCCESS DDI_SUCCESS #define PCICFG_FAILURE DDI_FAILURE +#define PCICFG_ALL_FUNC 0xffffffff + /* * The following subclass definition for Non Transparent bridge should * be moved to pci.h. diff --git a/usr/src/uts/common/sys/hotplug/pci/pcie_hp.h b/usr/src/uts/common/sys/hotplug/pci/pcie_hp.h new file mode 100644 index 0000000000..8810c5203c --- /dev/null +++ b/usr/src/uts/common/sys/hotplug/pci/pcie_hp.h @@ -0,0 +1,332 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_PCIE_HP_H +#define _SYS_PCIE_HP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _KERNEL +#include <sys/ddi_hp.h> +#include <sys/pcie_impl.h> +#endif /* _KERNEL */ +#include "../../../../../common/pci/pci_strings.h" +#include <sys/hotplug/pci/pcihp.h> + +#define PCIEHPC_PROP_HELP "help" +#define PCIEHPC_PROP_ALL "all" +#define PCIEHPC_PROP_LED_FAULT "fault_led" +#define PCIEHPC_PROP_LED_POWER "power_led" +#define PCIEHPC_PROP_LED_ATTN "attn_led" +#define PCIEHPC_PROP_LED_ACTIVE "active_led" +#define PCIEHPC_PROP_CARD_TYPE "card_type" +#define PCIEHPC_PROP_BOARD_TYPE "board_type" +#define PCIEHPC_PROP_SLOT_CONDITION "slot_condition" + +#define PCIEHPC_PROP_VALUE_UNKNOWN "unknown" +#define PCIEHPC_PROP_VALUE_ON "on" +#define PCIEHPC_PROP_VALUE_OFF "off" +#define PCIEHPC_PROP_VALUE_BLINK "blink" +#define PCIEHPC_PROP_VALUE_PCIHOTPLUG "pci hotplug" +#define PCIEHPC_PROP_VALUE_OK "ok" +#define PCIEHPC_PROP_VALUE_FAILING "failing" +#define PCIEHPC_PROP_VALUE_FAILED "failed" +#define PCIEHPC_PROP_VALUE_UNUSABLE "unusable" +#define PCIEHPC_PROP_VALUE_LED "<on|off|blink>" +#define PCIEHPC_PROP_VALUE_TYPE "<type description>" +#define PCIEHPC_PROP_VALUE_CONDITION "<unknown|ok|failing|failed|unusable>" + +/* condition */ +#define PCIEHPC_PROP_COND_OK "ok" +#define PCIEHPC_PROP_COND_FAILING "failing" +#define PCIEHPC_PROP_COND_FAILED "failed" +#define PCIEHPC_PROP_COND_UNUSABLE "unusable" +#define PCIEHPC_PROP_COND_UNKNOWN "unknown" + +#ifdef _KERNEL + +#define PCIE_HP_MAX_SLOTS 31 /* Max # of slots */ +#define PCIE_HP_CMD_WAIT_TIME 10000 /* Delay in microseconds */ +#define PCIE_HP_CMD_WAIT_RETRY 100 /* Max retry count */ +#define PCIE_HP_DLL_STATE_CHANGE_TIMEOUT 1 /* Timeout in seconds */ +#define PCIE_HP_POWER_GOOD_WAIT_TIME 220000 /* Wait time after issuing a */ + /* cmd to change slot state */ + +/* definations for PCIEHPC/PCISHPC */ +#define PCIE_NATIVE_HP_TYPE "PCIe-Native" /* PCIe Native type */ +#define PCIE_ACPI_HP_TYPE "PCIe-ACPI" /* PCIe ACPI type */ +#define PCIE_PROP_HP_TYPE "PCIe-Proprietary" /* PCIe Prop type */ +#define PCIE_PCI_HP_TYPE "PCI-SHPC" /* PCI (SHPC) type */ + +#define PCIE_GET_HP_CTRL(dip) \ + (pcie_hp_ctrl_t *)PCIE_DIP2BUS(dip)->bus_hp_ctrl + +#define PCIE_SET_HP_CTRL(dip, ctrl_p) \ + (PCIE_DIP2BUS(dip)->bus_hp_ctrl) = (pcie_hp_ctrl_t *)ctrl_p + +#define PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p) \ + ((bus_p->bus_hp_sup_modes & PCIE_ACPI_HP_MODE) || \ + (bus_p->bus_hp_sup_modes & PCIE_NATIVE_HP_MODE)) + +#define PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p) \ + (bus_p->bus_hp_sup_modes & PCIE_PCI_HP_MODE) + +#define PCIE_IS_PCIE_HOTPLUG_ENABLED(bus_p) \ + ((bus_p->bus_hp_curr_mode == PCIE_ACPI_HP_MODE) || \ + (bus_p->bus_hp_curr_mode == PCIE_NATIVE_HP_MODE)) + +#define PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p) \ + (bus_p->bus_hp_curr_mode & PCIE_PCI_HP_MODE) + +typedef struct pcie_hp_ctrl pcie_hp_ctrl_t; +typedef struct pcie_hp_slot pcie_hp_slot_t; + +/* + * Maximum length of the string converted from the digital number of pci device + * number and function number, including the string's end mark. For example, + * device number 0 and function number 255 (ARI case), then the length is + * (1 + 3 + 1). + */ +#define PCIE_HP_DEV_FUNC_NUM_STRING_LEN 5 + +/* + * Length of the characters in a PCI port name. + * The format of the PCI port name is: pci.d,f where d is device number, f is + * function number. The constant string and characters are "pci." and ",". + */ +#define PCIE_HP_PORT_NAME_STRING_LEN 5 + +/* Platform specific ops (Native HP, ACPI, etc.) */ +typedef struct pcie_hp_ops { + /* initialize/setup hot plug controller hw */ + int (*init_hpc_hw)(pcie_hp_ctrl_t *ctrl_p); + + /* uninitialize hot plug controller hw */ + int (*uninit_hpc_hw)(pcie_hp_ctrl_t *ctrl_p); + + /* initialize slot information structure */ + int (*init_hpc_slotinfo)(pcie_hp_ctrl_t *ctrl_p); + + /* uninitialize slot information structure */ + int (*uninit_hpc_slotinfo)(pcie_hp_ctrl_t *ctrl_p); + + /* slot poweron */ + int (*poweron_hpc_slot)(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t *result); + + /* slot poweroff */ + /* uninitialize hot plug controller hw */ + int (*poweroff_hpc_slot)(pcie_hp_slot_t *slot_p, + ddi_hp_cn_state_t *result); + + /* enable hot plug interrupts/events */ + int (*enable_hpc_intr)(pcie_hp_ctrl_t *ctrl_p); + + /* disable hot plug interrupts/events */ + int (*disable_hpc_intr)(pcie_hp_ctrl_t *ctrl_p); +} pcie_hp_ops_t; + +/* Slot occupant information structure */ +#define PCIE_HP_MAX_OCCUPANTS 128 +typedef struct pcie_hp_occupant_info { + int i; + char *id[PCIE_HP_MAX_OCCUPANTS]; +} pcie_hp_occupant_info_t; + +/* + * pcie_hp_led_t + * + * Type definitions for LED type + */ +typedef enum { + PCIE_HP_FAULT_LED, + PCIE_HP_POWER_LED, + PCIE_HP_ATTN_LED, + PCIE_HP_ACTIVE_LED +} pcie_hp_led_t; + +/* + * pcie_hp_led_state_t + * + * Type definitions for LED state + */ +typedef enum { + PCIE_HP_LED_OFF, + PCIE_HP_LED_ON, + PCIE_HP_LED_BLINK +} pcie_hp_led_state_t; + +/* + * PCI and PCI Express Hotplug slot structure + */ +struct pcie_hp_slot { + uint32_t hs_num; /* Logical slot number */ + uint32_t hs_phy_slot_num; /* Physical slot number */ + uint32_t hs_device_num; /* PCI device num for slot */ + uint16_t hs_minor; /* Minor num for this slot */ + ddi_hp_cn_info_t hs_info; /* Slot information */ + ddi_hp_cn_state_t hs_state; /* Slot state */ + + pcie_hp_led_state_t hs_power_led_state; /* Power LED state */ + pcie_hp_led_state_t hs_attn_led_state; /* Attn LED state */ + pcie_hp_led_state_t hs_active_led_state; /* Active LED state */ + pcie_hp_led_state_t hs_fault_led_state; /* Fault LED state */ + + ap_condition_t hs_condition; /* Condition of the slot. */ + /* For cfgadm condition. */ + + /* Synchronization variable(s) for hot plug events */ + kcondvar_t hs_attn_btn_cv; /* ATTN button pressed intr */ + boolean_t hs_attn_btn_pending; + kthread_t *hs_attn_btn_threadp; /* ATTN button event thread */ + boolean_t hs_attn_btn_thread_exit; + kcondvar_t hs_dll_active_cv; /* DLL State Changed intr */ + + pcie_hp_ctrl_t *hs_ctrl; /* Hotplug ctrl for this slot */ +}; + +/* + * Register ops for read/write of non-standard HPC (e.g: OPL platform). + */ +typedef struct pcie_hp_regops { + uint_t (*get)(void *cookie, off_t offset); + uint_t (*put)(void *cookie, off_t offset, uint_t val); + void *cookie; +} pcie_hp_regops_t; + +/* + * PCI and PCI Express Hotplug controller structure + */ +struct pcie_hp_ctrl { + dev_info_t *hc_dip; /* DIP for HP controller */ + kmutex_t hc_mutex; /* Mutex for this ctrl */ + uint_t hc_flags; /* Misc flags */ + + /* Slot information */ + pcie_hp_slot_t *hc_slots[PCIE_HP_MAX_SLOTS]; /* Slot pointers */ + boolean_t hc_has_attn; /* Do we have attn btn? */ + boolean_t hc_has_mrl; /* Do we have MRL? */ + kcondvar_t hc_cmd_comp_cv; /* Command Completion intr */ + boolean_t hc_cmd_pending; /* Command completion pending */ + + /* PCI Express Hotplug specific fields */ + boolean_t hc_has_emi_lock; /* Do we have EMI Lock? */ + boolean_t hc_dll_active_rep; /* Report DLL DL_Active state */ + pcie_hp_ops_t hc_ops; /* Platform specific ops */ + /* (Native, ACPI) */ + + /* PCI Hotplug (SHPC) specific fields */ + uint32_t hc_num_slots_impl; /* # of HP Slots Implemented */ + uint32_t hc_num_slots_connected; /* # of HP Slots Connected */ + int hc_curr_bus_speed; /* Current Bus Speed */ + uint32_t hc_device_start; /* 1st PCI Device # */ + uint32_t hc_phys_start; /* 1st Phys Device # */ + uint32_t hc_device_increases; /* Device # Increases */ + boolean_t hc_arbiter_timeout; /* Got a Arb timeout IRQ */ + + /* Register read/write ops for non-standard HPC (e.g: OPL) */ + pcie_hp_regops_t hc_regops; + + /* Platform implementation specific data if any: ACPI, CK804,... */ + void *hc_misc_data; +}; + +/* + * Control structure for tree walk during configure/unconfigure operation. + */ +typedef struct pcie_hp_cn_cfg_t { + void *slotp; + boolean_t flag; /* Flag to ignore errors */ + int rv; /* Return error code */ + dev_info_t *dip; /* dip at which the (first) */ + /* error occurred */ + void *cn_private; /* Connection specific data */ +} pcie_hp_cn_cfg_t; + +/* + * arg for unregistering port of a pci bridge + */ +typedef struct pcie_hp_unreg_port { + /* pci bridge dip to which the port is associated */ + dev_info_t *nexus_dip; + /* + * Connector number of the physical slot whose dependent ports will be + * unregistered. If NULL, then all the ports of the pci bridge dip will + * be unregistered. + */ + int connector_num; + int rv; +} pcie_hp_unreg_port_t; + +/* + * arg for getting a port's state + */ +typedef struct pcie_hp_port_state { + char *cn_name; + ddi_hp_cn_state_t cn_state; + int rv; +} pcie_hp_port_state_t; + +/* hc_flags */ +#define PCIE_HP_INITIALIZED_FLAG (1 << 0) /* HPC initialized */ + +/* PCIe hotplug friendly functions */ +extern int pcie_hp_init(dev_info_t *dip, caddr_t arg); +extern int pcie_hp_uninit(dev_info_t *dip); +extern int pcie_hp_intr(dev_info_t *dip); +extern int pcie_hp_probe(pcie_hp_slot_t *slot_p); +extern int pcie_hp_unprobe(pcie_hp_slot_t *slot_p); +extern int pcie_hp_common_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op, + void *arg, void *result); +extern dev_info_t *pcie_hp_devi_find(dev_info_t *dip, uint_t device, + uint_t function); +extern void pcie_hp_create_occupant_props(dev_info_t *self, dev_t dev, + int pci_dev); +extern void pcie_hp_create_occupant_props(dev_info_t *self, dev_t dev, + int pci_dev); +extern void pcie_hp_delete_occupant_props(dev_info_t *dip, dev_t dev); +extern int pcie_copyin_nvlist(char *packed_buf, size_t packed_sz, + nvlist_t **nvlp); +extern int pcie_copyout_nvlist(nvlist_t *nvl, char *packed_buf, + size_t *packed_sz); +extern char *pcie_led_state_text(pcie_hp_led_state_t state); +extern char *pcie_slot_condition_text(ap_condition_t condition); +extern int pcie_create_minor_node(pcie_hp_ctrl_t *, int); +extern void pcie_remove_minor_node(pcie_hp_ctrl_t *, int); +extern void pcie_hp_gen_sysevent_req(char *slot_name, int hint, + dev_info_t *self, int kmflag); + +extern const struct pci_class_strings_s class_pci[]; +extern int class_pci_items; + +#endif /* _KERNEL */ + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_PCIE_HP_H */ diff --git a/usr/src/uts/common/sys/hotplug/pci/pciehpc.h b/usr/src/uts/common/sys/hotplug/pci/pciehpc.h index dd1ad7c416..1d57720559 100644 --- a/usr/src/uts/common/sys/hotplug/pci/pciehpc.h +++ b/usr/src/uts/common/sys/hotplug/pci/pciehpc.h @@ -2,9 +2,8 @@ * 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. + * 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. @@ -19,17 +18,14 @@ * * CDDL HEADER END */ - /* - * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYS_HOTPLUG_PCI_PCIEHPC_H #define _SYS_HOTPLUG_PCI_PCIEHPC_H -#pragma ident "%Z%%M% %I% %E% SMI" - #ifdef __cplusplus extern "C" { #endif @@ -37,17 +33,22 @@ extern "C" { /* * Interfaces exported by PCI-E nexus Hot Plug Controller extension module */ - -/* register ops for read/write of non-standard HPC (e.g: OPL platform) */ -typedef struct pciehpc_regops { - uint_t (*get)(void *cookie, off_t offset); - uint_t (*put)(void *cookie, off_t offset, uint_t val); - void *cookie; -} pciehpc_regops_t; - -int pciehpc_init(dev_info_t *, pciehpc_regops_t *); -int pciehpc_uninit(dev_info_t *); -int pciehpc_intr(dev_info_t *); +int pciehpc_init(dev_info_t *dip, caddr_t arg); +int pciehpc_uninit(dev_info_t *dip); +int pciehpc_intr(dev_info_t *dip); +int pciehpc_hp_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op, void *arg, + void *result); +void pciehpc_get_slot_state(pcie_hp_slot_t *slot_p); +void pciehpc_set_slot_name(pcie_hp_ctrl_t *ctrl_p); +uint8_t pciehpc_reg_get8(pcie_hp_ctrl_t *ctrl_p, uint_t off); +uint16_t pciehpc_reg_get16(pcie_hp_ctrl_t *ctrl_p, uint_t off); +uint32_t pciehpc_reg_get32(pcie_hp_ctrl_t *ctrl_p, uint_t off); +void pciehpc_reg_put8(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint8_t val); +void pciehpc_reg_put16(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint16_t val); +void pciehpc_reg_put32(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint32_t val); +#if defined(__i386) || defined(__amd64) +extern void pciehpc_update_ops(pcie_hp_ctrl_t *ctrl_p); +#endif /* defined(__i386) || defined(__amd64) */ #ifdef __cplusplus } diff --git a/usr/src/uts/common/sys/hotplug/pci/pciehpc_impl.h b/usr/src/uts/common/sys/hotplug/pci/pciehpc_impl.h deleted file mode 100644 index b2915d15e9..0000000000 --- a/usr/src/uts/common/sys/hotplug/pci/pciehpc_impl.h +++ /dev/null @@ -1,233 +0,0 @@ -/* - * 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 2008 Sun Microsystems, Inc. All rights reserved. - * Use is subject to license terms. - */ - -#ifndef _SYS_HOTPLUG_PCI_PCIEHPC_IMPL_H -#define _SYS_HOTPLUG_PCI_PCIEHPC_IMPL_H - -#pragma ident "%Z%%M% %I% %E% SMI" - -#ifdef __cplusplus -extern "C" { -#endif - -#include <sys/disp.h> -#include <sys/stat.h> -#include <sys/condvar.h> -#include <sys/pcie.h> -#include <sys/hotplug/hpcsvc.h> -#include <sys/hotplug/pci/pciehpc.h> - -/* - * PCI Express Hot Plug slot softstate structure - * - */ -typedef struct pciehpc_slot -{ - hpc_slot_info_t slot_info; /* HPS framework slot info */ - hpc_slot_t slot_handle; /* HPS framework handle */ - hpc_slot_ops_t slot_ops; /* HPS framework callbacks */ - uint32_t fault_led_state; /* Fault LED state */ - uint32_t power_led_state; /* Power LED state */ - uint32_t attn_led_state; /* Attn LED state */ - uint32_t active_led_state; /* Active LED state */ - hpc_slot_state_t slot_state; /* Slot State */ - uint32_t slotNum; /* slot number */ - /* synchronization variable(s) for hot plug events */ - kcondvar_t cmd_comp_cv; /* Command Completion intr. */ - boolean_t command_pending; - kcondvar_t attn_btn_cv; /* ATTN button pressed intr */ - boolean_t attn_btn_pending; - kthread_t *attn_btn_threadp; /* ATTN button event thread */ - boolean_t attn_btn_thread_exit; - kcondvar_t dll_active_cv; /* DLL State Changed intr */ -} pciehpc_slot_t; - -typedef enum { - PCIEHPC_NATIVE_HP_MODE, PCIEHPC_ACPI_HP_MODE -} pciehpc_hp_mode_t; - -typedef uint32_t pciehpc_soft_state_t; - -/* init_flags */ -#define PCIEHPC_SOFT_STATE_UNINITIALIZED 0x01 -#define PCIEHPC_SOFT_STATE_INITIALIZED 0x02 -#define PCIEHPC_SOFT_STATE_INIT_HTABLE 0x04 -#define PCIEHPC_SOFT_STATE_INIT_ALLOC 0x08 -#define PCIEHPC_SOFT_STATE_INIT_HANDLER 0x10 -#define PCIEHPC_SOFT_STATE_INIT_ENABLE 0x20 -#define PCIEHPC_SOFT_STATE_INIT_BLOCK 0x40 -#define PCIEHPC_SOFT_STATE_INIT_FM 0x80 -#define PCIEHPC_SOFT_STATE_PCIE_DEV 0x10000 - -/* - * PCI Express Hotplug controller soft state structure - */ -typedef struct pciehpc -{ - dev_info_t *dip; /* DIP for the Nexus */ - uint8_t bus; /* primary bus number */ - uint8_t dev; /* device number */ - uint8_t func; /* function number */ - kmutex_t pciehpc_mutex; /* Mutex for this ctrl */ - pciehpc_soft_state_t soft_state; /* soft state flags */ - pciehpc_hp_mode_t hp_mode; /* HP mode (Native, ACPI) */ - struct pciehpc *nextp; /* Linked list pointer */ - - /* PCIE Hot Plug Controller register access */ - ddi_acc_handle_t cfghdl; /* PCI cfg access handle */ - caddr_t regs_base; /* config regs base */ - uint_t pcie_caps_reg_offset; /* offset to PCIE Cap regs */ - - /* slot information */ - pciehpc_slot_t slot; /* Slot info */ - boolean_t has_attn; /* Do we have attn btn? */ - boolean_t has_mrl; /* Do we have MRL? */ - boolean_t has_emi_lock; /* Do we have EMI Lock? */ - - /* link capablities */ - boolean_t dll_active_rep; /* Do we report DLL DL_Active state? */ - - /* register read/write ops for non-standard HPC (e.g: OPL) */ - pciehpc_regops_t regops; - - /* platform specific ops (Native HP, ACPI, etc.) */ - struct pciehpc_ops { - /* initialize/setup hot plug controller hw */ - int (*init_hpc_hw)(struct pciehpc *ctrl_p); - /* initialize slot information structure */ - int (*init_hpc_slotinfo)(struct pciehpc *ctrl_p); - /* disable hot plug interrupts/events */ - int (*disable_hpc_intr)(struct pciehpc *ctrl_p); - /* enable hot plug interrupts/events */ - int (*enable_hpc_intr)(struct pciehpc *ctrl_p); - /* uninitialize hot plug controller hw */ - int (*uninit_hpc_hw)(struct pciehpc *ctrl_p); - /* uninitialize slot information structure */ - int (*uninit_hpc_slotinfo)(struct pciehpc *ctrl_p); - /* probe for HPC */ - int (*probe_hpc)(struct pciehpc *ctrl_p); - } ops; - - /* platform implementation specific data if any: ACPI, CK804,... */ - void *misc_data; -} pciehpc_t; - -typedef struct pciehpc_ops pciehpc_ops_t; - -/* - * PCI-E HPC Command Completion delay in microseconds and the max retry - * count. - */ -#define PCIEHPC_CMD_WAIT_TIME 10000 -#define PCIEHPC_CMD_WAIT_RETRY 100 - -/* - * PCI-E HPC Dll State Change time out in seconds - */ -#define PCIEHPC_DLL_STATE_CHANGE_TIMEOUT 1 - -#define SLOTCTL_SUPPORTED_INTRS_MASK \ - (PCIE_SLOTCTL_ATTN_BTN_EN \ - | PCIE_SLOTCTL_PWR_FAULT_EN \ - | PCIE_SLOTCTL_MRL_SENSOR_EN \ - | PCIE_SLOTCTL_PRESENCE_CHANGE_EN \ - | PCIE_SLOTCTL_CMD_INTR_EN \ - | PCIE_SLOTCTL_HP_INTR_EN \ - | PCIE_SLOTCTL_DLL_STATE_EN) - -#define SLOT_STATUS_EVENTS \ - (PCIE_SLOTSTS_ATTN_BTN_PRESSED \ - | PCIE_SLOTSTS_PWR_FAULT_DETECTED \ - | PCIE_SLOTSTS_MRL_SENSOR_CHANGED \ - | PCIE_SLOTSTS_COMMAND_COMPLETED \ - | PCIE_SLOTSTS_PRESENCE_CHANGED \ - | PCIE_SLOTSTS_DLL_STATE_CHANGED) - -/* - * function prototype defintions for common native mode functions in - * PCIEHPC module. - */ -int pciehpc_hpc_init(pciehpc_t *ctrl_p); -int pciehpc_hpc_uninit(pciehpc_t *ctrl_p); -int pciehpc_slotinfo_init(pciehpc_t *ctrl_p); -int pciehpc_enable_intr(pciehpc_t *ctrl_p); -int pciehpc_disable_intr(pciehpc_t *ctrl_p); -int pciehpc_slotinfo_uninit(pciehpc_t *ctrl_p); -int pciehpc_probe_hpc(pciehpc_t *ctrl_p); -hpc_led_state_t pciehpc_led_state_to_hpc(uint16_t state); -uint16_t pciehpc_led_state_to_pciehpc(hpc_led_state_t state); -hpc_led_state_t pciehpc_get_led_state(pciehpc_t *ctrl_p, hpc_led_t led); -void pciehpc_set_led_state(pciehpc_t *ctrl_p, hpc_led_t led, - hpc_led_state_t state); -int pciehpc_slot_connect(caddr_t ops_arg, hpc_slot_t slot_hdl, - void *data, uint_t flags); -int pciehpc_slot_disconnect(caddr_t ops_arg, hpc_slot_t slot_hdl, - void *data, uint_t flags); -int pciehpc_slot_control(caddr_t ops_arg, hpc_slot_t slot_hdl, - int request, caddr_t arg); -void pciehpc_get_slot_state(pciehpc_t *ctrl_p); -void pciehpc_issue_hpc_command(pciehpc_t *ctrl_p, uint16_t control); -int pciehpc_regs_setup(dev_info_t *dip, uint_t rnum, offset_t off, - caddr_t *addrp, ddi_acc_handle_t *handle); -void pciehpc_regs_teardown(ddi_acc_handle_t *handle); -int pciehpc_register_slot(pciehpc_t *ctrl_p); -int pciehpc_unregister_slot(pciehpc_t *ctrl_p); -uint8_t pciehpc_reg_get8(pciehpc_t *ctrl_p, uint_t off); -uint16_t pciehpc_reg_get16(pciehpc_t *ctrl_p, uint_t off); -uint32_t pciehpc_reg_get32(pciehpc_t *ctrl_p, uint_t off); -void pciehpc_reg_put8(pciehpc_t *ctrl_p, uint_t off, uint8_t val); -void pciehpc_reg_put16(pciehpc_t *ctrl_p, uint_t off, uint16_t val); -void pciehpc_reg_put32(pciehpc_t *ctrl_p, uint_t off, uint32_t val); -void pciehpc_set_slot_name(pciehpc_t *ctrl_p); - -#if defined(__i386) || defined(__amd64) -void pciehpc_update_ops(pciehpc_t *ctrl_p); -#endif /* defined(__i386) || defined(__amd64) */ - -#ifdef DEBUG -extern int pciehpc_debug; -#define PCIEHPC_DEBUG(args) if (pciehpc_debug >= 1) cmn_err args -#define PCIEHPC_DEBUG2(args) if (pciehpc_debug >= 2) cmn_err args -#define PCIEHPC_DEBUG3(args) if (pciehpc_debug >= 3) cmn_err args -#else -#define PCIEHPC_DEBUG(args) -#define PCIEHPC_DEBUG2(args) -#define PCIEHPC_DEBUG3(args) -#endif - -/* default interrupt priority for Hot Plug interrupts */ -#define PCIEHPC_INTR_PRI 1 - -#define PCIE_ENABLE_ERRORS(arg1) \ - pcie_enable_errors(arg1); \ - (void) pcie_enable_ce(arg1) -#define PCIE_DISABLE_ERRORS(arg1) pcie_disable_errors(arg1) - -#ifdef __cplusplus -} -#endif - -#endif /* _SYS_HOTPLUG_PCI_PCIEHPC_IMPL_H */ diff --git a/usr/src/uts/common/sys/hotplug/pci/pcihp.h b/usr/src/uts/common/sys/hotplug/pci/pcihp.h index 75a4ee0c2d..eb96e924bd 100644 --- a/usr/src/uts/common/sys/hotplug/pci/pcihp.h +++ b/usr/src/uts/common/sys/hotplug/pci/pcihp.h @@ -19,15 +19,13 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SYS_HOTPLUG_PCI_PCIHP_H #define _SYS_HOTPLUG_PCI_PCIHP_H -#pragma ident "%Z%%M% %I% %E% SMI" - #ifdef __cplusplus extern "C" { #endif diff --git a/usr/src/uts/common/sys/hotplug/pci/pcishpc.h b/usr/src/uts/common/sys/hotplug/pci/pcishpc.h index 81443bbaa4..12b9afd30a 100644 --- a/usr/src/uts/common/sys/hotplug/pci/pcishpc.h +++ b/usr/src/uts/common/sys/hotplug/pci/pcishpc.h @@ -18,32 +18,26 @@ * * CDDL HEADER END */ - /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#ifndef _SYS_PCISHPC_H -#define _SYS_PCISHPC_H - -#pragma ident "%Z%%M% %I% %E% SMI" +#ifndef _SYS_HOTPLUG_PCI_PCISHPC_H +#define _SYS_HOTPLUG_PCI_PCISHPC_H #ifdef __cplusplus extern "C" { #endif -/* - * Interfaces exported by SHPC Nexus extension module - */ - -int pcishpc_init(dev_info_t *); -int pcishpc_uninit(dev_info_t *); -int pcishpc_intr(dev_info_t *); +int pcishpc_init(dev_info_t *dip); +int pcishpc_uninit(dev_info_t *dip); +int pcishpc_intr(dev_info_t *dip); +int pcishpc_hp_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op, void *arg, + void *result); -#define PCISHPC_INTR_PRI (LOCK_LEVEL - 1) #ifdef __cplusplus } #endif -#endif /* _SYS_PCISHPC_H */ +#endif /* _SYS_HOTPLUG_PCI_PCISHPC_H */ diff --git a/usr/src/uts/common/sys/hotplug/pci/pcishpc_regs.h b/usr/src/uts/common/sys/hotplug/pci/pcishpc_regs.h deleted file mode 100755 index d487067f59..0000000000 --- a/usr/src/uts/common/sys/hotplug/pci/pcishpc_regs.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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. - */ - -#ifndef _SYS_HOTPLUG_PCI_PCISHPC_REGS_H -#define _SYS_HOTPLUG_PCI_PCISHPC_REGS_H - -#pragma ident "%Z%%M% %I% %E% SMI" - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * SHPC controller registers accessed via the SHPC DWORD select and DATA - * registers in PCI configuration space relative to the SHPC capibility - * pointer. - */ -#define SHPC_DWORD_SELECT_OFF 0x2 -#define SHPC_DWORD_DATA_OFF 0x4 - -#define SHPC_BASE_OFFSET_REG 0x00 -#define SHPC_SLOTS_AVAIL_I_REG 0x01 -#define SHPC_SLOTS_AVAIL_II_REG 0x02 -#define SHPC_SLOT_CONFIGURATION_REG 0x03 -#define SHPC_PROF_IF_SBCR_REG 0x04 -#define SHPC_COMMAND_STATUS_REG 0x05 -#define SHPC_IRQ_LOCATOR_REG 0x06 -#define SHPC_SERR_LOCATOR_REG 0x07 -#define SHPC_CTRL_SERR_INT_REG 0x08 -#define SHPC_LOGICAL_SLOT_REGS 0x09 -#define SHPC_VENDOR_SPECIFIC 0x28 - -#ifdef __cplusplus -} -#endif - -#endif /* _SYS_HOTPLUG_PCI_PCISHPC_REGS_H */ diff --git a/usr/src/uts/common/sys/modctl.h b/usr/src/uts/common/sys/modctl.h index 7ccd1d6dd4..3ac19b948e 100644 --- a/usr/src/uts/common/sys/modctl.h +++ b/usr/src/uts/common/sys/modctl.h @@ -269,6 +269,7 @@ struct modlinkage { #define MODISRETIRED 42 #define MODDEVEMPTYDIR 43 #define MODREMDRVALIAS 44 +#define MODHPOPS 45 /* * sub cmds for MODEVENTS @@ -289,6 +290,15 @@ struct modlinkage { #define MODDEVNAME_RECONFIG 4 #define MODDEVNAME_SYSAVAIL 5 +/* + * subcmds for MODHPOPS + */ +#define MODHPOPS_CHANGE_STATE 0 +#define MODHPOPS_CREATE_PORT 1 +#define MODHPOPS_REMOVE_PORT 2 +#define MODHPOPS_BUS_GET 3 +#define MODHPOPS_BUS_SET 4 + /* * Data structure passed to modconfig command in kernel to build devfs tree diff --git a/usr/src/uts/common/sys/pci.h b/usr/src/uts/common/sys/pci.h index efb19097e9..9d38b5bb37 100644 --- a/usr/src/uts/common/sys/pci.h +++ b/usr/src/uts/common/sys/pci.h @@ -603,7 +603,7 @@ extern "C" { #define PCI_CAP_ID_VS 0x9 /* Vendor Specific */ #define PCI_CAP_ID_DEBUG_PORT 0xA /* Debug Port supported */ #define PCI_CAP_ID_cPCI_CRC 0xB /* CompactPCI central resource ctrl */ -#define PCI_CAP_ID_PCI_HOTPLUG 0xC /* PCI Hot Plug supported */ +#define PCI_CAP_ID_PCI_HOTPLUG 0xC /* PCI Hot Plug (SHPC) supported */ #define PCI_CAP_ID_P2P_SUBSYS 0xD /* PCI bridge Sub-system ID */ #define PCI_CAP_ID_AGP_8X 0xE /* AGP 8X supported */ #define PCI_CAP_ID_SECURE_DEV 0xF /* Secure Device supported */ @@ -796,6 +796,104 @@ typedef struct pcix_attr { #define PCI_PCIX_BSS_SPL_DLY 0x20 /* Secondary split comp delayed */ /* + * PCI Hotplug capability entry offsets + * + * SHPC based PCI hotplug controller registers accessed via the DWORD + * select and DATA registers in PCI configuration space relative to the + * PCI HP capibility pointer. + */ +#define PCI_HP_DWORD_SELECT_OFF 0x2 +#define PCI_HP_DWORD_DATA_OFF 0x4 + +#define PCI_HP_BASE_OFFSET_REG 0x00 +#define PCI_HP_SLOTS_AVAIL_I_REG 0x01 +#define PCI_HP_SLOTS_AVAIL_II_REG 0x02 +#define PCI_HP_SLOT_CONFIGURATION_REG 0x03 +#define PCI_HP_PROF_IF_SBCR_REG 0x04 +#define PCI_HP_COMMAND_STATUS_REG 0x05 +#define PCI_HP_IRQ_LOCATOR_REG 0x06 +#define PCI_HP_SERR_LOCATOR_REG 0x07 +#define PCI_HP_CTRL_SERR_INT_REG 0x08 +#define PCI_HP_LOGICAL_SLOT_REGS 0x09 +#define PCI_HP_VENDOR_SPECIFIC 0x28 + +/* Definitions used with the PCI_HP_SLOTS_AVAIL_I_REG register */ +#define PCI_HP_AVAIL_33MHZ_CONV_SPEED_SHIFT 0 +#define PCI_HP_AVAIL_66MHZ_PCIX_SPEED_SHIFT 8 +#define PCI_HP_AVAIL_100MHZ_PCIX_SPEED_SHIFT 16 +#define PCI_HP_AVAIL_133MHZ_PCIX_SPEED_SHIFT 24 +#define PCI_HP_AVAIL_SPEED_MASK 0x1F + +/* Definitions used with the PCI_HP_SLOTS_AVAIL_II_REG register */ +#define PCI_HP_AVAIL_66MHZ_CONV_SPEED_SHIFT 0 + +/* Register bits used with the PCI_HP_PROF_IF_SBCR_REG register */ +#define PCI_HP_SBCR_33MHZ_CONV_SPEED 0x0 +#define PCI_HP_SBCR_66MHZ_CONV_SPEED 0x1 +#define PCI_HP_SBCR_66MHZ_PCIX_SPEED 0x2 +#define PCI_HP_SBCR_100MHZ_PCIX_SPEED 0x3 +#define PCI_HP_SBCR_133MHZ_PCIX_SPEED 0x4 +#define PCI_HP_SBCR_SPEED_MASK 0x7 + +/* Register bits used with the PCI_HP_COMMAND_STATUS_REG register */ +#define PCI_HP_COMM_STS_ERR_INVALID_SPEED 0x80000 +#define PCI_HP_COMM_STS_ERR_INVALID_COMMAND 0x40000 +#define PCI_HP_COMM_STS_ERR_MRL_OPEN 0x20000 +#define PCI_HP_COMM_STS_ERR_MASK 0xe0000 +#define PCI_HP_COMM_STS_CTRL_BUSY 0x10000 +#define PCI_HP_COMM_STS_SET_SPEED 0x40 + +/* Register bits used with the PCI_HP_CTRL_SERR_INT_REG register */ +#define PCI_HP_SERR_INT_GLOBAL_IRQ_MASK 0x1 +#define PCI_HP_SERR_INT_GLOBAL_SERR_MASK 0x2 +#define PCI_HP_SERR_INT_CMD_COMPLETE_MASK 0x4 +#define PCI_HP_SERR_INT_ARBITER_SERR_MASK 0x8 +#define PCI_HP_SERR_INT_CMD_COMPLETE_IRQ 0x10000 +#define PCI_HP_SERR_INT_ARBITER_IRQ 0x20000 +#define PCI_HP_SERR_INT_MASK_ALL 0xf + +/* Register bits used with the PCI_HP_LOGICAL_SLOT_REGS register */ +#define PCI_HP_SLOT_POWER_ONLY 0x1 +#define PCI_HP_SLOT_ENABLED 0x2 +#define PCI_HP_SLOT_DISABLED 0x3 +#define PCI_HP_SLOT_STATE_MASK 0x3 +#define PCI_HP_SLOT_MRL_STATE_MASK 0x100 +#define PCI_HP_SLOT_66MHZ_CONV_CAPABLE 0x200 +#define PCI_HP_SLOT_CARD_EMPTY_MASK 0xc00 +#define PCI_HP_SLOT_66MHZ_PCIX_CAPABLE 0x1000 +#define PCI_HP_SLOT_100MHZ_PCIX_CAPABLE 0x2000 +#define PCI_HP_SLOT_133MHZ_PCIX_CAPABLE 0x3000 +#define PCI_HP_SLOT_PCIX_CAPABLE_MASK 0x3000 +#define PCI_HP_SLOT_PCIX_CAPABLE_SHIFT 12 +#define PCI_HP_SLOT_PRESENCE_DETECTED 0x10000 +#define PCI_HP_SLOT_ISO_PWR_DETECTED 0x20000 +#define PCI_HP_SLOT_ATTN_DETECTED 0x40000 +#define PCI_HP_SLOT_MRL_DETECTED 0x80000 +#define PCI_HP_SLOT_POWER_DETECTED 0x100000 +#define PCI_HP_SLOT_PRESENCE_MASK 0x1000000 +#define PCI_HP_SLOT_ISO_PWR_MASK 0x2000000 +#define PCI_HP_SLOT_ATTN_MASK 0x4000000 +#define PCI_HP_SLOT_MRL_MASK 0x8000000 +#define PCI_HP_SLOT_POWER_MASK 0x10000000 +#define PCI_HP_SLOT_MRL_SERR_MASK 0x20000000 +#define PCI_HP_SLOT_POWER_SERR_MASK 0x40000000 +#define PCI_HP_SLOT_MASK_ALL 0x5f000000 + +/* Register bits used with the PCI_HP_IRQ_LOCATOR_REG register */ +#define PCI_HP_IRQ_CMD_COMPLETE 0x1 +#define PCI_HP_IRQ_SLOT_N_PENDING 0x2 + +/* Register bits used with the PCI_HP_SERR_LOCATOR_REG register */ +#define PCI_HP_IRQ_SERR_ARBITER_PENDING 0x1 +#define PCI_HP_IRQ_SERR_SLOT_N_PENDING 0x2 + +/* Register bits used with the PCI_HP_SLOT_CONFIGURATION_REG register */ +#define PCI_HP_SLOT_CONFIG_MRL_SENSOR 0x40000000 +#define PCI_HP_SLOT_CONFIG_ATTN_BUTTON 0x80000000 +#define PCI_HP_SLOT_CONFIG_PHY_SLOT_NUM_SHIFT 16 +#define PCI_HP_SLOT_CONFIG_PHY_SLOT_NUM_MASK 0x3FF + +/* * PCI Message Signalled Interrupts (MSI) capability entry offsets for 32-bit */ #define PCI_MSI_CTRL 0x02 /* MSI control register, 2 bytes */ diff --git a/usr/src/uts/common/sys/pci_impl.h b/usr/src/uts/common/sys/pci_impl.h index 0dc0679bd4..d485800b96 100644 --- a/usr/src/uts/common/sys/pci_impl.h +++ b/usr/src/uts/common/sys/pci_impl.h @@ -26,7 +26,6 @@ #ifndef _SYS_PCI_IMPL_H #define _SYS_PCI_IMPL_H - #include <sys/dditypes.h> #include <sys/memlist.h> @@ -133,6 +132,24 @@ extern int memlist_count(struct memlist *); #endif /* __i386 || __amd64 */ +/* Definitions for minor numbers */ +#define PCI_MINOR_NUM(x, y) (((uint_t)(x) << 8) | ((y) & 0xFF)) +#define PCI_MINOR_NUM_TO_PCI_DEVNUM(x) ((x) & 0xFF) +#define PCI_MINOR_NUM_TO_INSTANCE(x) ((x) >> 8) +#define PCI_DEVCTL_MINOR 0xFF + +/* + * Minor numbers for dedicated pcitool nodes. + * Note that FF and FE minor numbers are used for other minor nodes. + */ +#define PCI_TOOL_REG_MINOR_NUM 0xFD +#define PCI_TOOL_INTR_MINOR_NUM 0xFC + +/* pci devctl soft state flag */ +#define PCI_SOFT_STATE_CLOSED 0x0 +#define PCI_SOFT_STATE_OPEN 0x1 +#define PCI_SOFT_STATE_OPEN_EXCL 0x2 + /* * PCI capability related definitions. */ diff --git a/usr/src/uts/common/sys/pcie.h b/usr/src/uts/common/sys/pcie.h index 6ffa4ac033..05b70a56fa 100644 --- a/usr/src/uts/common/sys/pcie.h +++ b/usr/src/uts/common/sys/pcie.h @@ -50,6 +50,15 @@ extern "C" { #define PCIE_SLOTSTS 0x1A /* Slot Status */ #define PCIE_ROOTCTL 0x1C /* Root Control */ #define PCIE_ROOTSTS 0x20 /* Root Status */ +#define PCIE_DEVCAP2 0x24 /* Device Capability 2 */ +#define PCIE_DEVCTL2 0x28 /* Device Control 2 */ +#define PCIE_DEVSTS2 0x2A /* Device Status 2 */ +#define PCIE_LINKCAP2 0x2C /* Link Capability 2 */ +#define PCIE_LINKCTL2 0x30 /* Link Control 2 */ +#define PCIE_LINKSTS2 0x32 /* Link Status 2 */ +#define PCIE_SLOTCAP2 0x34 /* Slot Capability 2 */ +#define PCIE_SLOTCTL2 0x38 /* Slot Control 2 */ +#define PCIE_SLOTSTS2 0x3A /* Slot Status 2 */ /* * PCI-Express Config Space size @@ -60,6 +69,7 @@ extern "C" { * PCI-Express Capabilities Register (2 bytes) */ #define PCIE_PCIECAP_VER_1_0 0x1 /* PCI-E spec 1.0 */ +#define PCIE_PCIECAP_VER_2_0 0x2 /* PCI-E spec 2.0 */ #define PCIE_PCIECAP_VER_MASK 0xF /* Version Mask */ #define PCIE_PCIECAP_DEV_TYPE_PCIE_DEV 0x00 /* PCI-E Endpont Device */ #define PCIE_PCIECAP_DEV_TYPE_PCI_DEV 0x10 /* "Leg PCI" Endpont Device */ @@ -303,6 +313,7 @@ extern "C" { #define PCIE_SLOTCTL_DLL_STATE_EN 0x1000 /* DLL State Changed En */ #define PCIE_SLOTCTL_ATTN_INDICATOR_MASK 0x00C0 /* Attn Indicator mask */ #define PCIE_SLOTCTL_PWR_INDICATOR_MASK 0x0300 /* Power Indicator mask */ +#define PCIE_SLOTCTL_INTR_MASK 0x103f /* Supported intr mask */ /* State values for the Power and Attention Indicators */ #define PCIE_SLOTCTL_INDICATOR_STATE_ON 0x1 /* indicator ON */ @@ -334,6 +345,7 @@ extern "C" { #define PCIE_SLOTSTS_PRESENCE_DETECTED 0x40 /* Card Present in slot */ #define PCIE_SLOTSTS_EMI_LOCK_SET 0x0080 /* EMI Lock set */ #define PCIE_SLOTSTS_DLL_STATE_CHANGED 0x0100 /* DLL State Changed */ +#define PCIE_SLOTSTS_STATUS_EVENTS 0x11f /* Supported events */ /* * Root Control Register (2 bytes) @@ -352,6 +364,49 @@ extern "C" { #define PCIE_ROOTSTS_PME_STATUS 0x10000 /* PME Status */ #define PCIE_ROOTSTS_PME_PENDING 0x20000 /* PME Pending */ +/* + * Device Capabilities 2 Register (4 bytes) + */ +#define PCIE_DEVCAP2_COM_TO_RANGE_MASK 0xF +#define PCIE_DEVCAP2_COM_TO_DISABLE 0x10 +#define PCIE_DEVCAP2_ARI_FORWARD 0x20 +#define PCIE_DEVCAP2_ATOMICOP_ROUTING 0x40 +#define PCIE_DEVCAP2_32_ATOMICOP_COMPL 0x80 +#define PCIE_DEVCAP2_64_ATOMICOP_COMPL 0x100 +#define PCIE_DEVCAP2_128_CAS_COMPL 0x200 +#define PCIE_DEVCAP2_NO_RO_PR_PR_PASS 0x400 +#define PCIE_DEVCAP2_LTR_MECH 0x800 +#define PCIE_DEVCAP2_TPH_COMP_SHIFT 12 +#define PCIE_DEVCAP2_TPH_COMP_MASK 0x3 +#define PCIE_DEVCAP2_EXT_FMT_FIELD 0x100000 +#define PCIE_DEVCAP2_END_END_TLP_PREFIX 0x200000 +#define PCIE_DEVCAP2_MAX_END_END_SHIFT 22 +#define PCIE_DEVCAP2_MAX_END_END_MASK 0x3 + +/* + * Device Control 2 Register (2 bytes) + */ +#define PCIE_DEVCTL2_COM_TO_RANGE_0 0x0 +#define PCIE_DEVCTL2_COM_TO_RANGE_1 0x1 +#define PCIE_DEVCTL2_COM_TO_RANGE_2 0x2 +#define PCIE_DEVCTL2_COM_TO_RANGE_3 0x5 +#define PCIE_DEVCTL2_COM_TO_RANGE_4 0x6 +#define PCIE_DEVCTL2_COM_TO_RANGE_5 0x9 +#define PCIE_DEVCTL2_COM_TO_RANGE_6 0xa +#define PCIE_DEVCTL2_COM_TO_RANGE_7 0xd +#define PCIE_DEVCTL2_COM_TO_RANGE_8 0xe +#define PCIE_DEVCTL2_COM_TO_DISABLE 0x10 +#define PCIE_DEVCTL2_ARI_FORWARD_EN 0x20 +#define PCIE_DEVCTL2_ATOMICOP_REQ_EN 0x40 +#define PCIE_DEVCTL2_ATOMICOP_EGRS_BLK 0x80 +#define PCIE_DEVCTL2_IDO_REQ_EN 0x100 +#define PCIE_DEVCTL2_IDO_COMPL_EN 0x200 +#define PCIE_DEVCTL2_LTR_MECH_EN 0x400 +#define PCIE_DEVCTL2_END_END_TLP_PREFIX 0x8000 + + + + /* * PCI-Express Enhanced Capabilities Link Entry Bit Offsets @@ -549,6 +604,25 @@ extern "C" { #define PCIE_SER_SID_UPPER_DW 0x8 /* Upper 32-bit Serial Number */ /* + * ARI Capability Offsets + */ +#define PCIE_ARI_HDR 0x0 /* Enhanced Capability Header */ +#define PCIE_ARI_CAP 0x4 /* ARI Capability Register */ +#define PCIE_ARI_CTL 0x6 /* ARI Control Register */ + +#define PCIE_ARI_CAP_MFVC_FUNC_GRP 0x01 +#define PCIE_ARI_CAP_ASC_FUNC_GRP 0x02 + +#define PCIE_ARI_CAP_NEXT_FUNC_SHIFT 8 +#define PCIE_ARI_CAP_NEXT_FUNC_MASK 0xffff + +#define PCIE_ARI_CTRL_MFVC_FUNC_GRP 0x01 +#define PCIE_ARI_CTRL_ASC_FUNC_GRP 0x02 + +#define PCIE_ARI_CTRL_FUNC_GRP_SHIFT 4 +#define PCIE_ARI_CTRL_FUNC_GRP_MASK 0x7 + +/* * PCI-E Common TLP Header Fields */ #define PCIE_TLP_FMT_3DW 0x00 @@ -595,6 +669,7 @@ typedef uint16_t pcie_req_id_t; #define PCIE_REQ_ID_DEV_MASK 0x00F8 #define PCIE_REQ_ID_FUNC_SHIFT 0 #define PCIE_REQ_ID_FUNC_MASK 0x0007 +#define PCIE_REQ_ID_ARI_FUNC_MASK 0x00FF #define PCIE_CPL_STS_SUCCESS 0 #define PCIE_CPL_STS_UR 1 diff --git a/usr/src/uts/common/sys/pcie_impl.h b/usr/src/uts/common/sys/pcie_impl.h index 1e88407b1c..0943da2c92 100644 --- a/usr/src/uts/common/sys/pcie_impl.h +++ b/usr/src/uts/common/sys/pcie_impl.h @@ -65,6 +65,14 @@ extern "C" { #define PCIE_HAS_AER(bus_p) (bus_p->bus_aer_off) /* IS_ROOT = is RC or RP */ #define PCIE_IS_ROOT(bus_p) (PCIE_IS_RC(bus_p) || PCIE_IS_RP(bus_p)) + +#define PCIE_IS_HOTPLUG_CAPABLE(dip) \ + (PCIE_DIP2BUS(dip)->bus_hp_sup_modes) + +#define PCIE_IS_HOTPLUG_ENABLED(dip) \ + ((PCIE_DIP2BUS(dip)->bus_hp_curr_mode == PCIE_PCI_HP_MODE) || \ + (PCIE_DIP2BUS(dip)->bus_hp_curr_mode == PCIE_NATIVE_HP_MODE)) + /* * This is a pseudo pcie "device type", but it's needed to explain describe * nodes such as PX and NPE, which aren't really PCI devices but do control or @@ -154,6 +162,14 @@ extern "C" { #define PFD_IS_RC(pfd_p) PCIE_IS_RC(PCIE_PFD2BUS(pfd_p)) #define PFD_IS_RP(pfd_p) PCIE_IS_RP(PCIE_PFD2BUS(pfd_p)) +/* bus_hp_mode field */ +typedef enum { + PCIE_NONE_HP_MODE = 0x0, + PCIE_ACPI_HP_MODE = 0x1, + PCIE_PCI_HP_MODE = 0x2, + PCIE_NATIVE_HP_MODE = 0x4 +} pcie_hp_mode_t; + typedef struct pf_pci_bdg_err_regs { uint16_t pci_bdg_sec_stat; /* PCI secondary status reg */ uint16_t pci_bdg_ctrl; /* PCI bridge control reg */ @@ -244,8 +260,9 @@ typedef struct pcie_bus { /* Needed for PCI/PCIe fabric error handling */ dev_info_t *bus_dip; dev_info_t *bus_rp_dip; - ddi_acc_handle_t bus_cfg_hdl; /* error handling acc handle */ + ddi_acc_handle_t bus_cfg_hdl; /* error handling acc hdle */ uint_t bus_fm_flags; + uint_t bus_soft_state; /* Static PCI/PCIe information */ pcie_req_id_t bus_bdf; @@ -258,6 +275,7 @@ typedef struct pcie_bus { uint16_t bus_pcie_off; /* PCIe Capability Offset */ uint16_t bus_aer_off; /* PCIe Advanced Error Offset */ uint16_t bus_pcix_off; /* PCIx Capability Offset */ + uint16_t bus_pci_hp_off; /* PCI HP (SHPC) Cap Offset */ uint16_t bus_ecc_ver; /* PCIX ecc version */ pci_bus_range_t bus_bus_range; /* pci bus-range property */ ppb_ranges_t *bus_addr_ranges; /* pci range property */ @@ -271,6 +289,11 @@ typedef struct pcie_bus { int bus_mps; /* Maximum Payload Size */ void *bus_plat_private; /* Platform specific */ + /* Hotplug specific fields */ + pcie_hp_mode_t bus_hp_sup_modes; /* HP modes supported */ + pcie_hp_mode_t bus_hp_curr_mode; /* HP mode used */ + void *bus_hp_ctrl; /* HP bus ctrl data */ + int bus_ari; /* ARI device */ } pcie_bus_t; struct pf_data { @@ -355,8 +378,28 @@ typedef struct { int highest_common_mps; } pcie_max_supported_t; +/* + * Default interrupt priority for all PCI and PCIe nexus drivers including + * hotplug interrupts. + */ +#define PCIE_INTR_PRI (LOCK_LEVEL - 1) + +/* + * XXX - PCIE_IS_PCIE check is required in order not to invoke these macros + * for non-standard PCI or PCI Express Hotplug Controllers. + */ +#define PCIE_ENABLE_ERRORS(dip) \ + if (PCIE_IS_PCIE(PCIE_DIP2BUS(dip))) { \ + pcie_enable_errors(dip); \ + (void) pcie_enable_ce(dip); \ + } + +#define PCIE_DISABLE_ERRORS(dip) \ + if (PCIE_IS_PCIE(PCIE_DIP2BUS(dip))) { \ + pcie_disable_errors(dip); \ + } + #ifdef DEBUG -extern uint_t pcie_debug_flags; #define PCIE_DBG pcie_dbg /* Common Debugging shortcuts */ #define PCIE_DBG_CFG(dip, bus_p, name, sz, off, org) \ @@ -372,18 +415,29 @@ extern uint_t pcie_debug_flags; ddi_get_instance(dip), bus_p->bus_bdf, name, off, org, \ PCIE_AER_GET(sz, bus_p, off)) -extern void pcie_dbg(char *fmt, ...); - #else /* DEBUG */ #define PCIE_DBG_CFG 0 && #define PCIE_DBG 0 && +#define PCIE_ARI_DBG 0 && #define PCIE_DBG_CAP 0 && #define PCIE_DBG_AER 0 && #endif /* DEBUG */ /* PCIe Friendly Functions */ +extern int pcie_init(dev_info_t *dip, caddr_t arg); +extern int pcie_uninit(dev_info_t *dip); +extern int pcie_intr(dev_info_t *dip); +extern int pcie_open(dev_info_t *dip, dev_t *devp, int flags, int otyp, + cred_t *credp); +extern int pcie_close(dev_info_t *dip, dev_t dev, int flags, int otyp, + cred_t *credp); +extern int pcie_ioctl(dev_info_t *dip, dev_t dev, int cmd, intptr_t arg, + int mode, cred_t *credp, int *rvalp); +extern int pcie_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, + int flags, char *name, caddr_t valuep, int *lengthp); + extern void pcie_init_root_port_mps(dev_info_t *dip); extern int pcie_initchild(dev_info_t *dip); extern void pcie_uninitchild(dev_info_t *dip); @@ -422,6 +476,26 @@ extern void pcie_set_aer_suce_mask(uint32_t mask); extern void pcie_set_serr_mask(uint32_t mask); extern void pcie_init_plat(dev_info_t *dip); extern void pcie_fini_plat(dev_info_t *dip); +extern int pcie_read_only_probe(dev_info_t *, char *, dev_info_t **); +extern dev_info_t *pcie_func_to_dip(dev_info_t *dip, pcie_req_id_t function); +extern int pcie_ari_disable(dev_info_t *dip); +extern int pcie_ari_enable(dev_info_t *dip); + +#define PCIE_ARI_FORW_NOT_SUPPORTED 0 +#define PCIE_ARI_FORW_SUPPORTED 1 + +extern int pcie_ari_supported(dev_info_t *dip); + +#define PCIE_ARI_FORW_DISABLED 0 +#define PCIE_ARI_FORW_ENABLED 1 + +extern int pcie_ari_is_enabled(dev_info_t *dip); + +#define PCIE_NOT_ARI_DEVICE 0 +#define PCIE_ARI_DEVICE 1 + +extern int pcie_ari_device(dev_info_t *dip); +extern int pcie_ari_get_next_function(dev_info_t *dip, int *func); /* PCIe error handling functions */ extern int pf_scan_fabric(dev_info_t *rpdip, ddi_fm_error_t *derr, @@ -432,6 +506,11 @@ extern int pf_hdl_lookup(dev_info_t *, uint64_t, uint32_t, uint64_t, pcie_req_id_t); extern int pf_tlp_decode(pcie_bus_t *, pf_pcie_adv_err_regs_t *); +#ifdef DEBUG +extern uint_t pcie_debug_flags; +extern void pcie_dbg(char *fmt, ...); +#endif /* DEBUG */ + #ifdef __cplusplus } #endif diff --git a/usr/src/uts/common/sys/sunndi.h b/usr/src/uts/common/sys/sunndi.h index 22e61db408..f17ab18a7c 100644 --- a/usr/src/uts/common/sys/sunndi.h +++ b/usr/src/uts/common/sys/sunndi.h @@ -42,11 +42,15 @@ extern "C" { #define NDI_SUCCESS DDI_SUCCESS /* successful return */ #define NDI_FAILURE DDI_FAILURE /* unsuccessful return */ -#define NDI_NOMEM -2 /* failed to allocate resources */ -#define NDI_BADHANDLE -3 /* bad handle passed to in function */ -#define NDI_FAULT -4 /* fault during copyin/copyout */ -#define NDI_BUSY -5 /* device busy - could not offline */ -#define NDI_UNBOUND -6 /* device not bound to a driver */ +#define NDI_NOMEM -2 /* failed to allocate resources */ +#define NDI_BADHANDLE -3 /* bad handle passed to in function */ +#define NDI_FAULT -4 /* fault during copyin/copyout */ +#define NDI_BUSY -5 /* device busy - could not offline */ +#define NDI_UNBOUND -6 /* device not bound to a driver */ +#define NDI_EINVAL -7 /* invalid request or arguments */ +#define NDI_ENOTSUP -8 /* operation or event not supported */ +#define NDI_CLAIMED NDI_SUCCESS /* event is claimed */ +#define NDI_UNCLAIMED -9 /* event is not claimed */ /* * Property functions: See also, ddipropdefs.h. @@ -636,6 +640,23 @@ int ndi_busop_bus_unconfig(dev_info_t *dip, uint_t flags, ddi_bus_config_op_t op, void *arg); +/* + * Called by the Nexus/HPC drivers to register, unregister and interact + * with the hotplug framework for the specified hotplug connection. + */ +int +ndi_hp_register(dev_info_t *dip, ddi_hp_cn_info_t *info_p); + +int +ndi_hp_unregister(dev_info_t *dip, char *cn_name); + +int +ndi_hp_state_change_req(dev_info_t *dip, char *cn_name, + ddi_hp_cn_state_t state, uint_t flag); + +void +ndi_hp_walk_cn(dev_info_t *dip, int (*f)(ddi_hp_cn_info_t *, void *), + void *arg); /* * Bus Resource allocation structures and function prototypes exported @@ -705,12 +726,9 @@ typedef struct ndi_ra_request { #define NDI_RA_TYPE_PCI_PREFETCH_MEM "pci_prefetchable_memory" #define NDI_RA_TYPE_INTR "interrupt" - - /* flag bit definition */ #define NDI_RA_PASS 0x0001 /* pass request up the dev tree */ - /* * Prototype definitions for functions exported */ @@ -729,7 +747,6 @@ int ndi_ra_free(dev_info_t *dip, uint64_t base, uint64_t len, char *type, uint_t flag); - /* * ndi_dev_is_prom_node: Return non-zero if the node is a prom node */ |