root/usr/src/uts/common/io/scsi/adapters/pmcs/pmcs_subr.c
/*
 * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * This file contains various support routines.
 */

#include <sys/scsi/adapters/pmcs/pmcs.h>

/*
 * Local static data
 */
static int tgtmap_stable_usec = MICROSEC;       /* 1 second */
static int tgtmap_csync_usec = 10 * MICROSEC;   /* 10 seconds */

/*
 * SAS Topology Configuration
 */
static void pmcs_new_tport(pmcs_hw_t *, pmcs_phy_t *);
static void pmcs_configure_expander(pmcs_hw_t *, pmcs_phy_t *, pmcs_iport_t *);

static void pmcs_check_expanders(pmcs_hw_t *, pmcs_phy_t *);
static void pmcs_check_expander(pmcs_hw_t *, pmcs_phy_t *);
static void pmcs_clear_expander(pmcs_hw_t *, pmcs_phy_t *, int);

static int pmcs_expander_get_nphy(pmcs_hw_t *, pmcs_phy_t *);
static int pmcs_expander_content_discover(pmcs_hw_t *, pmcs_phy_t *,
    pmcs_phy_t *);

static int pmcs_smp_function_result(pmcs_hw_t *, smp_response_frame_t *);
static void pmcs_flush_nonio_cmds(pmcs_hw_t *pwp, pmcs_xscsi_t *tgt);
static boolean_t pmcs_validate_devid(pmcs_phy_t *, pmcs_phy_t *, uint32_t);
static void pmcs_clear_phys(pmcs_hw_t *, pmcs_phy_t *);
static int pmcs_configure_new_devices(pmcs_hw_t *, pmcs_phy_t *);
static void pmcs_begin_observations(pmcs_hw_t *);
static void pmcs_flush_observations(pmcs_hw_t *);
static boolean_t pmcs_report_observations(pmcs_hw_t *);
static boolean_t pmcs_report_iport_observations(pmcs_hw_t *, pmcs_iport_t *,
    pmcs_phy_t *);
#ifdef DEBUG
static pmcs_phy_t *pmcs_find_phy_needing_work(pmcs_hw_t *, pmcs_phy_t *);
#endif
static int pmcs_kill_devices(pmcs_hw_t *, pmcs_phy_t *);
static void pmcs_lock_phy_impl(pmcs_phy_t *, int);
static void pmcs_unlock_phy_impl(pmcs_phy_t *, int);
static pmcs_phy_t *pmcs_clone_phy(pmcs_phy_t *);
static boolean_t pmcs_configure_phy(pmcs_hw_t *, pmcs_phy_t *);
static void pmcs_reap_dead_phy(pmcs_phy_t *);
static pmcs_iport_t *pmcs_get_iport_by_ua(pmcs_hw_t *, char *);
static boolean_t pmcs_phy_target_match(pmcs_phy_t *);
static void pmcs_iport_active(pmcs_iport_t *);
static void pmcs_tgtmap_activate_cb(void *, char *, scsi_tgtmap_tgt_type_t,
    void **);
static boolean_t pmcs_tgtmap_deactivate_cb(void *, char *,
    scsi_tgtmap_tgt_type_t, void *, scsi_tgtmap_deact_rsn_t);
static void pmcs_add_dead_phys(pmcs_hw_t *, pmcs_phy_t *);
static void pmcs_get_fw_version(pmcs_hw_t *);
static int pmcs_get_time_stamp(pmcs_hw_t *, uint64_t *, hrtime_t *);

/*
 * Often used strings
 */
const char pmcs_nowrk[] = "%s: unable to get work structure";
const char pmcs_nomsg[] = "%s: unable to get Inbound Message entry";
const char pmcs_timeo[] = "%s: command timed out";

extern const ddi_dma_attr_t pmcs_dattr;
extern kmutex_t pmcs_trace_lock;

/*
 * Some Initial setup steps.
 */

int
pmcs_setup(pmcs_hw_t *pwp)
{
        uint32_t barval = pwp->mpibar;
        uint32_t i, scratch, regbar, regoff, barbar, baroff;
        uint32_t new_ioq_depth, ferr = 0;

        /*
         * Check current state. If we're not at READY state,
         * we can't go further.
         */
        scratch = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH1);
        if ((scratch & PMCS_MSGU_AAP_STATE_MASK) == PMCS_MSGU_AAP_STATE_ERROR) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: AAP Error State (0x%x)",
                    __func__, pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH1) &
                    PMCS_MSGU_AAP_ERROR_MASK);
                pmcs_fm_ereport(pwp, DDI_FM_DEVICE_INVAL_STATE);
                ddi_fm_service_impact(pwp->dip, DDI_SERVICE_LOST);
                return (-1);
        }
        if ((scratch & PMCS_MSGU_AAP_STATE_MASK) != PMCS_MSGU_AAP_STATE_READY) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: AAP unit not ready (state 0x%x)",
                    __func__, scratch & PMCS_MSGU_AAP_STATE_MASK);
                pmcs_fm_ereport(pwp, DDI_FM_DEVICE_INVAL_STATE);
                ddi_fm_service_impact(pwp->dip, DDI_SERVICE_LOST);
                return (-1);
        }

        /*
         * Read the offset from the Message Unit scratchpad 0 register.
         * This allows us to read the MPI Configuration table.
         *
         * Check its signature for validity.
         */
        baroff = barval;
        barbar = barval >> PMCS_MSGU_MPI_BAR_SHIFT;
        baroff &= PMCS_MSGU_MPI_OFFSET_MASK;

        regoff = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH0);
        regbar = regoff >> PMCS_MSGU_MPI_BAR_SHIFT;
        regoff &= PMCS_MSGU_MPI_OFFSET_MASK;

        if (regoff > baroff) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: bad MPI Table Length (register offset=0x%08x, "
                    "passed offset=0x%08x)", __func__, regoff, baroff);
                return (-1);
        }
        if (regbar != barbar) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: bad MPI BAR (register BAROFF=0x%08x, "
                    "passed BAROFF=0x%08x)", __func__, regbar, barbar);
                return (-1);
        }
        pwp->mpi_offset = regoff;
        if (pmcs_rd_mpi_tbl(pwp, PMCS_MPI_AS) != PMCS_SIGNATURE) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: Bad MPI Configuration Table Signature 0x%x", __func__,
                    pmcs_rd_mpi_tbl(pwp, PMCS_MPI_AS));
                return (-1);
        }

        if (pmcs_rd_mpi_tbl(pwp, PMCS_MPI_IR) != PMCS_MPI_REVISION1) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: Bad MPI Configuration Revision 0x%x", __func__,
                    pmcs_rd_mpi_tbl(pwp, PMCS_MPI_IR));
                return (-1);
        }

        /*
         * Generate offsets for the General System, Inbound Queue Configuration
         * and Outbound Queue configuration tables. This way the macros to
         * access those tables will work correctly.
         */
        pwp->mpi_gst_offset =
            pwp->mpi_offset + pmcs_rd_mpi_tbl(pwp, PMCS_MPI_GSTO);
        pwp->mpi_iqc_offset =
            pwp->mpi_offset + pmcs_rd_mpi_tbl(pwp, PMCS_MPI_IQCTO);
        pwp->mpi_oqc_offset =
            pwp->mpi_offset + pmcs_rd_mpi_tbl(pwp, PMCS_MPI_OQCTO);

        pmcs_get_fw_version(pwp);

        pwp->max_cmd = pmcs_rd_mpi_tbl(pwp, PMCS_MPI_MOIO);
        pwp->max_dev = pmcs_rd_mpi_tbl(pwp, PMCS_MPI_INFO0) >> 16;

        pwp->max_iq = PMCS_MNIQ(pmcs_rd_mpi_tbl(pwp, PMCS_MPI_INFO1));
        pwp->max_oq = PMCS_MNOQ(pmcs_rd_mpi_tbl(pwp, PMCS_MPI_INFO1));
        pwp->nphy = PMCS_NPHY(pmcs_rd_mpi_tbl(pwp, PMCS_MPI_INFO1));
        if (pwp->max_iq <= PMCS_NIQ) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: not enough Inbound Queues supported "
                    "(need %d, max_oq=%d)", __func__, pwp->max_iq, PMCS_NIQ);
                return (-1);
        }
        if (pwp->max_oq <= PMCS_NOQ) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: not enough Outbound Queues supported "
                    "(need %d, max_oq=%d)", __func__, pwp->max_oq, PMCS_NOQ);
                return (-1);
        }
        if (pwp->nphy == 0) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: zero phys reported", __func__);
                return (-1);
        }
        if (PMCS_HPIQ(pmcs_rd_mpi_tbl(pwp, PMCS_MPI_INFO1))) {
                pwp->hipri_queue = (1 << PMCS_IQ_OTHER);
        }


        for (i = 0; i < pwp->nphy; i++) {
                PMCS_MPI_EVQSET(pwp, PMCS_OQ_EVENTS, i);
                PMCS_MPI_NCQSET(pwp, PMCS_OQ_EVENTS, i);
        }

        pmcs_wr_mpi_tbl(pwp, PMCS_MPI_INFO2,
            (PMCS_OQ_EVENTS << GENERAL_EVENT_OQ_SHIFT) |
            (PMCS_OQ_EVENTS << DEVICE_HANDLE_REMOVED_SHIFT));

        /*
         * Verify that ioq_depth is valid (> 0 and not so high that it
         * would cause us to overrun the chip with commands).
         */
        if (pwp->ioq_depth == 0) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: I/O queue depth set to 0. Setting to %d",
                    __func__, PMCS_NQENTRY);
                pwp->ioq_depth = PMCS_NQENTRY;
        }

        if (pwp->ioq_depth < PMCS_MIN_NQENTRY) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: I/O queue depth set too low (%d). Setting to %d",
                    __func__, pwp->ioq_depth, PMCS_MIN_NQENTRY);
                pwp->ioq_depth = PMCS_MIN_NQENTRY;
        }

        if (pwp->ioq_depth > (pwp->max_cmd / (PMCS_IO_IQ_MASK + 1))) {
                new_ioq_depth = pwp->max_cmd / (PMCS_IO_IQ_MASK + 1);
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: I/O queue depth set too high (%d). Setting to %d",
                    __func__, pwp->ioq_depth, new_ioq_depth);
                pwp->ioq_depth = new_ioq_depth;
        }

        /*
         * Allocate consistent memory for OQs and IQs.
         */
        pwp->iqp_dma_attr = pwp->oqp_dma_attr = pmcs_dattr;
        pwp->iqp_dma_attr.dma_attr_align =
            pwp->oqp_dma_attr.dma_attr_align = PMCS_QENTRY_SIZE;

        /*
         * The Rev C chip has the ability to do PIO to or from consistent
         * memory anywhere in a 64 bit address space, but the firmware is
         * not presently set up to do so.
         */
        pwp->iqp_dma_attr.dma_attr_addr_hi =
            pwp->oqp_dma_attr.dma_attr_addr_hi = 0x000000FFFFFFFFFFull;

        for (i = 0; i < PMCS_NIQ; i++) {
                if (pmcs_dma_setup(pwp, &pwp->iqp_dma_attr,
                    &pwp->iqp_acchdls[i],
                    &pwp->iqp_handles[i], PMCS_QENTRY_SIZE * pwp->ioq_depth,
                    (caddr_t *)&pwp->iqp[i], &pwp->iqaddr[i]) == B_FALSE) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                            "Failed to setup DMA for iqp[%d]", i);
                        return (-1);
                }
                bzero(pwp->iqp[i], PMCS_QENTRY_SIZE * pwp->ioq_depth);
        }

        for (i = 0; i < PMCS_NOQ; i++) {
                if (pmcs_dma_setup(pwp, &pwp->oqp_dma_attr,
                    &pwp->oqp_acchdls[i],
                    &pwp->oqp_handles[i], PMCS_QENTRY_SIZE * pwp->ioq_depth,
                    (caddr_t *)&pwp->oqp[i], &pwp->oqaddr[i]) == B_FALSE) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                            "Failed to setup DMA for oqp[%d]", i);
                        return (-1);
                }
                bzero(pwp->oqp[i], PMCS_QENTRY_SIZE * pwp->ioq_depth);
        }

        /*
         * Install the IQ and OQ addresses (and null out the rest).
         */
        for (i = 0; i < pwp->max_iq; i++) {
                pwp->iqpi_offset[i] = pmcs_rd_iqc_tbl(pwp, PMCS_IQPIOFFX(i));
                if (i < PMCS_NIQ) {
                        if (i != PMCS_IQ_OTHER) {
                                pmcs_wr_iqc_tbl(pwp, PMCS_IQC_PARMX(i),
                                    pwp->ioq_depth | (PMCS_QENTRY_SIZE << 16));
                        } else {
                                pmcs_wr_iqc_tbl(pwp, PMCS_IQC_PARMX(i),
                                    (1 << 30) | pwp->ioq_depth |
                                    (PMCS_QENTRY_SIZE << 16));
                        }
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQBAHX(i),
                            DWORD1(pwp->iqaddr[i]));
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQBALX(i),
                            DWORD0(pwp->iqaddr[i]));
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQCIBAHX(i),
                            DWORD1(pwp->ciaddr+IQ_OFFSET(i)));
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQCIBALX(i),
                            DWORD0(pwp->ciaddr+IQ_OFFSET(i)));
                } else {
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQC_PARMX(i), 0);
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQBAHX(i), 0);
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQBALX(i), 0);
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQCIBAHX(i), 0);
                        pmcs_wr_iqc_tbl(pwp, PMCS_IQCIBALX(i), 0);
                }
        }

        for (i = 0; i < pwp->max_oq; i++) {
                pwp->oqci_offset[i] = pmcs_rd_oqc_tbl(pwp, PMCS_OQCIOFFX(i));
                if (i < PMCS_NOQ) {
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQC_PARMX(i), pwp->ioq_depth |
                            (PMCS_QENTRY_SIZE << 16) | OQIEX);
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQBAHX(i),
                            DWORD1(pwp->oqaddr[i]));
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQBALX(i),
                            DWORD0(pwp->oqaddr[i]));
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQPIBAHX(i),
                            DWORD1(pwp->ciaddr+OQ_OFFSET(i)));
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQPIBALX(i),
                            DWORD0(pwp->ciaddr+OQ_OFFSET(i)));
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQIPARM(i),
                            pwp->oqvec[i] << 24);
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQDICX(i), 0);
                } else {
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQC_PARMX(i), 0);
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQBAHX(i), 0);
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQBALX(i), 0);
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQPIBAHX(i), 0);
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQPIBALX(i), 0);
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQIPARM(i), 0);
                        pmcs_wr_oqc_tbl(pwp, PMCS_OQDICX(i), 0);
                }
        }

        /*
         * Set up logging, if defined.
         */
        if (pwp->fwlog) {
                uint64_t logdma = pwp->fwaddr;
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_MELBAH, DWORD1(logdma));
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_MELBAL, DWORD0(logdma));
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_MELBS, PMCS_FWLOG_SIZE >> 1);
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_MELSEV, pwp->fwlog);
                logdma += (PMCS_FWLOG_SIZE >> 1);
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_IELBAH, DWORD1(logdma));
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_IELBAL, DWORD0(logdma));
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_IELBS, PMCS_FWLOG_SIZE >> 1);
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_IELSEV, pwp->fwlog);
        }

        /*
         * Interrupt vectors, outbound queues, and odb_auto_clear
         *
         * MSI/MSI-X:
         * If we got 4 interrupt vectors, we'll assign one to each outbound
         * queue as well as the fatal interrupt, and auto clear can be set
         * for each.
         *
         * If we only got 2 vectors, one will be used for I/O completions
         * and the other for the other two vectors.  In this case, auto_
         * clear can only be set for I/Os, which is fine.  The fatal
         * interrupt will be mapped to the PMCS_FATAL_INTERRUPT bit, which
         * is not an interrupt vector.
         *
         * MSI/MSI-X/INT-X:
         * If we only got 1 interrupt vector, auto_clear must be set to 0,
         * and again the fatal interrupt will be mapped to the
         * PMCS_FATAL_INTERRUPT bit (again, not an interrupt vector).
         */

        switch (pwp->int_type) {
        case PMCS_INT_MSIX:
        case PMCS_INT_MSI:
                switch (pwp->intr_cnt) {
                case 1:
                        pmcs_wr_mpi_tbl(pwp, PMCS_MPI_FERR, PMCS_FERRIE |
                            (PMCS_FATAL_INTERRUPT << PMCS_FERIV_SHIFT));
                        pwp->odb_auto_clear = 0;
                        break;
                case 2:
                        pmcs_wr_mpi_tbl(pwp, PMCS_MPI_FERR, PMCS_FERRIE |
                            (PMCS_FATAL_INTERRUPT << PMCS_FERIV_SHIFT));
                        pwp->odb_auto_clear = (1 << PMCS_FATAL_INTERRUPT) |
                            (1 << PMCS_MSIX_IODONE);
                        break;
                case 4:
                        pmcs_wr_mpi_tbl(pwp, PMCS_MPI_FERR, PMCS_FERRIE |
                            (PMCS_MSIX_FATAL << PMCS_FERIV_SHIFT));
                        pwp->odb_auto_clear = (1 << PMCS_MSIX_FATAL) |
                            (1 << PMCS_MSIX_GENERAL) | (1 << PMCS_MSIX_IODONE) |
                            (1 << PMCS_MSIX_EVENTS);
                        break;
                }
                break;

        case PMCS_INT_FIXED:
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_FERR,
                    PMCS_FERRIE | (PMCS_FATAL_INTERRUPT << PMCS_FERIV_SHIFT));
                pwp->odb_auto_clear = 0;
                break;
        }

        /*
         * If the open retry interval is non-zero, set it.
         */
        if (pwp->open_retry_interval != 0) {
                int phynum;

                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: Setting open retry interval to %d usecs", __func__,
                    pwp->open_retry_interval);
                for (phynum = 0; phynum < pwp->nphy; phynum ++) {
                        pmcs_wr_gsm_reg(pwp, OPEN_RETRY_INTERVAL(phynum),
                            pwp->open_retry_interval);
                }
        }

        /*
         * Enable Interrupt Reassertion
         * Default Delay 1000us
         */
        ferr = pmcs_rd_mpi_tbl(pwp, PMCS_MPI_FERR);
        if ((ferr & PMCS_MPI_IRAE) == 0) {
                ferr &= ~(PMCS_MPI_IRAU | PMCS_MPI_IRAD_MASK);
                pmcs_wr_mpi_tbl(pwp, PMCS_MPI_FERR, ferr | PMCS_MPI_IRAE);
        }

        pmcs_wr_topunit(pwp, PMCS_OBDB_AUTO_CLR, pwp->odb_auto_clear);
        pwp->mpi_table_setup = 1;
        return (0);
}

/*
 * Start the Message Passing protocol with the PMC chip.
 */
int
pmcs_start_mpi(pmcs_hw_t *pwp)
{
        int i;

        pmcs_wr_msgunit(pwp, PMCS_MSGU_IBDB, PMCS_MSGU_IBDB_MPIINI);
        for (i = 0; i < 1000; i++) {
                if ((pmcs_rd_msgunit(pwp, PMCS_MSGU_IBDB) &
                    PMCS_MSGU_IBDB_MPIINI) == 0) {
                        break;
                }
                drv_usecwait(1000);
        }
        if (pmcs_rd_msgunit(pwp, PMCS_MSGU_IBDB) & PMCS_MSGU_IBDB_MPIINI) {
                return (-1);
        }
        drv_usecwait(500000);

        /*
         * Check to make sure we got to INIT state.
         */
        if (PMCS_MPI_S(pmcs_rd_gst_tbl(pwp, PMCS_GST_BASE)) !=
            PMCS_MPI_STATE_INIT) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: MPI launch failed (GST 0x%x DBCLR 0x%x)", __func__,
                    pmcs_rd_gst_tbl(pwp, PMCS_GST_BASE),
                    pmcs_rd_msgunit(pwp, PMCS_MSGU_IBDB_CLEAR));
                return (-1);
        }
        return (0);
}

/*
 * Stop the Message Passing protocol with the PMC chip.
 */
int
pmcs_stop_mpi(pmcs_hw_t *pwp)
{
        int i;

        for (i = 0; i < pwp->max_iq; i++) {
                pmcs_wr_iqc_tbl(pwp, PMCS_IQC_PARMX(i), 0);
                pmcs_wr_iqc_tbl(pwp, PMCS_IQBAHX(i), 0);
                pmcs_wr_iqc_tbl(pwp, PMCS_IQBALX(i), 0);
                pmcs_wr_iqc_tbl(pwp, PMCS_IQCIBAHX(i), 0);
                pmcs_wr_iqc_tbl(pwp, PMCS_IQCIBALX(i), 0);
        }
        for (i = 0; i < pwp->max_oq; i++) {
                pmcs_wr_oqc_tbl(pwp, PMCS_OQC_PARMX(i), 0);
                pmcs_wr_oqc_tbl(pwp, PMCS_OQBAHX(i), 0);
                pmcs_wr_oqc_tbl(pwp, PMCS_OQBALX(i), 0);
                pmcs_wr_oqc_tbl(pwp, PMCS_OQPIBAHX(i), 0);
                pmcs_wr_oqc_tbl(pwp, PMCS_OQPIBALX(i), 0);
                pmcs_wr_oqc_tbl(pwp, PMCS_OQIPARM(i), 0);
                pmcs_wr_oqc_tbl(pwp, PMCS_OQDICX(i), 0);
        }
        pmcs_wr_mpi_tbl(pwp, PMCS_MPI_FERR, 0);
        pmcs_wr_msgunit(pwp, PMCS_MSGU_IBDB, PMCS_MSGU_IBDB_MPICTU);
        for (i = 0; i < 2000; i++) {
                if ((pmcs_rd_msgunit(pwp, PMCS_MSGU_IBDB) &
                    PMCS_MSGU_IBDB_MPICTU) == 0) {
                        break;
                }
                drv_usecwait(1000);
        }
        if (pmcs_rd_msgunit(pwp, PMCS_MSGU_IBDB) & PMCS_MSGU_IBDB_MPICTU) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: MPI stop failed", __func__);
                return (-1);
        }
        return (0);
}

/*
 * Do a sequence of ECHO messages to test for MPI functionality,
 * all inbound and outbound queue functionality and interrupts.
 */
int
pmcs_echo_test(pmcs_hw_t *pwp)
{
        echo_test_t fred;
        struct pmcwork *pwrk;
        uint32_t *msg, count;
        int iqe = 0, iqo = 0, result, rval = 0;
        int iterations;
        hrtime_t echo_start, echo_end, echo_total;

        ASSERT(pwp->max_cmd > 0);

        /*
         * We want iterations to be max_cmd * 3 to ensure that we run the
         * echo test enough times to iterate through every inbound queue
         * at least twice.
         */
        iterations = pwp->max_cmd * 3;

        echo_total = 0;
        count = 0;

        while (count < iterations) {
                pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, NULL);
                if (pwrk == NULL) {
                        pmcs_prt(pwp, PMCS_PRT_ERR, NULL, NULL,
                            pmcs_nowrk, __func__);
                        rval = -1;
                        break;
                }

                mutex_enter(&pwp->iqp_lock[iqe]);
                msg = GET_IQ_ENTRY(pwp, iqe);
                if (msg == NULL) {
                        mutex_exit(&pwp->iqp_lock[iqe]);
                        pmcs_pwork(pwp, pwrk);
                        pmcs_prt(pwp, PMCS_PRT_ERR, NULL, NULL,
                            pmcs_nomsg, __func__);
                        rval = -1;
                        break;
                }

                bzero(msg, PMCS_QENTRY_SIZE);

                if (iqe == PMCS_IQ_OTHER) {
                        /* This is on the high priority queue */
                        msg[0] = LE_32(PMCS_HIPRI(pwp, iqo, PMCIN_ECHO));
                } else {
                        msg[0] = LE_32(PMCS_IOMB_IN_SAS(iqo, PMCIN_ECHO));
                }
                msg[1] = LE_32(pwrk->htag);
                fred.signature = 0xdeadbeef;
                fred.count = count;
                fred.ptr = &count;
                (void) memcpy(&msg[2], &fred, sizeof (fred));
                pwrk->state = PMCS_WORK_STATE_ONCHIP;

                INC_IQ_ENTRY(pwp, iqe);

                echo_start = gethrtime();
                DTRACE_PROBE2(pmcs__echo__test__wait__start,
                    hrtime_t, echo_start, uint32_t, pwrk->htag);

                if (++iqe == PMCS_NIQ) {
                        iqe = 0;
                }
                if (++iqo == PMCS_NOQ) {
                        iqo = 0;
                }

                WAIT_FOR(pwrk, 250, result);
                pmcs_pwork(pwp, pwrk);

                echo_end = gethrtime();
                DTRACE_PROBE2(pmcs__echo__test__wait__end,
                    hrtime_t, echo_end, int, result);
                echo_total += (echo_end - echo_start);

                if (result) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                            "%s: command timed out on echo test #%d",
                            __func__, count);
                        rval = -1;
                        break;
                }
        }

        /*
         * The intr_threshold is adjusted by PMCS_INTR_THRESHOLD in order to
         * remove the overhead of things like the delay in getting signaled
         * for completion.
         */
        if (echo_total != 0) {
                pwp->io_intr_coal.intr_latency =
                    (echo_total / iterations) / 2;
                pwp->io_intr_coal.intr_threshold =
                    PMCS_INTR_THRESHOLD(PMCS_QUANTUM_TIME_USECS * 1000 /
                    pwp->io_intr_coal.intr_latency);
        }

        return (rval);
}

/*
 * Start the (real) phys
 */
int
pmcs_start_phy(pmcs_hw_t *pwp, int phynum, int linkmode, int speed)
{
        int result;
        uint32_t *msg;
        struct pmcwork *pwrk;
        pmcs_phy_t *pptr;
        sas_identify_af_t sap;

        mutex_enter(&pwp->lock);
        pptr = pwp->root_phys + phynum;
        if (pptr == NULL) {
                mutex_exit(&pwp->lock);
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: cannot find port %d", __func__, phynum);
                return (0);
        }

        pmcs_lock_phy(pptr);
        mutex_exit(&pwp->lock);

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr);
        if (pwrk == NULL) {
                pmcs_unlock_phy(pptr);
                pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL, pmcs_nowrk, __func__);
                return (-1);
        }

        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        msg = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        if (msg == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_unlock_phy(pptr);
                pmcs_pwork(pwp, pwrk);
                pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL, pmcs_nomsg, __func__);
                return (-1);
        }
        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_EVENTS, PMCIN_PHY_START));
        msg[1] = LE_32(pwrk->htag);
        msg[2] = LE_32(linkmode | speed | phynum);
        bzero(&sap, sizeof (sap));
        sap.device_type = SAS_IF_DTYPE_ENDPOINT;
        sap.ssp_ini_port = 1;

        if (pwp->separate_ports) {
                pmcs_wwn2barray(pwp->sas_wwns[phynum], sap.sas_address);
        } else {
                pmcs_wwn2barray(pwp->sas_wwns[0], sap.sas_address);
        }

        ASSERT(phynum < SAS2_PHYNUM_MAX);
        sap.phy_identifier = phynum & SAS2_PHYNUM_MASK;
        (void) memcpy(&msg[3], &sap, sizeof (sas_identify_af_t));
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        pptr->state.prog_min_rate = (lowbit((ulong_t)speed) - 1);
        pptr->state.prog_max_rate = (highbit((ulong_t)speed) - 1);
        pptr->state.hw_min_rate = PMCS_HW_MIN_LINK_RATE;
        pptr->state.hw_max_rate = PMCS_HW_MAX_LINK_RATE;

        pmcs_unlock_phy(pptr);
        WAIT_FOR(pwrk, 1000, result);
        pmcs_pwork(pwp, pwrk);

        if (result) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL, pmcs_timeo, __func__);
        } else {
                mutex_enter(&pwp->lock);
                pwp->phys_started |= (1 << phynum);
                mutex_exit(&pwp->lock);
        }

        return (0);
}

int
pmcs_start_phys(pmcs_hw_t *pwp)
{
        int i, rval;

        for (i = 0; i < pwp->nphy; i++) {
                if ((pwp->phyid_block_mask & (1 << i)) == 0) {
                        if (pmcs_start_phy(pwp, i,
                            (pwp->phymode << PHY_MODE_SHIFT),
                            pwp->physpeed << PHY_LINK_SHIFT)) {
                                return (-1);
                        }
                        if (pmcs_clear_diag_counters(pwp, i)) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                                    "%s: failed to reset counters on PHY (%d)",
                                    __func__, i);
                        }
                }
        }

        rval = pmcs_get_time_stamp(pwp, &pwp->fw_timestamp, &pwp->hrtimestamp);
        if (rval) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: Failed to obtain firmware timestamp", __func__);
        } else {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "Firmware timestamp: 0x%" PRIx64, pwp->fw_timestamp);
        }

        return (0);
}

/*
 * Called with PHY locked
 */
int
pmcs_reset_phy(pmcs_hw_t *pwp, pmcs_phy_t *pptr, uint8_t type)
{
        uint32_t *msg;
        uint32_t iomb[(PMCS_QENTRY_SIZE << 1) >> 2];
        const char *mbar;
        uint32_t amt;
        uint32_t pdevid;
        uint32_t stsoff;
        uint32_t status;
        int result, level, phynum;
        struct pmcwork *pwrk;
        pmcs_iport_t *iport;
        uint32_t htag;

        ASSERT(mutex_owned(&pptr->phy_lock));

        bzero(iomb, PMCS_QENTRY_SIZE);
        phynum = pptr->phynum;
        level = pptr->level;
        if (level > 0) {
                pdevid = pptr->parent->device_id;
        } else if ((level == 0) && (pptr->dtype == EXPANDER)) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, pptr->target,
                    "%s: Not resetting HBA PHY @ %s", __func__, pptr->path);
                return (0);
        }

        if (!pptr->iport || !pptr->valid_device_id) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, pptr->target,
                    "%s: Can't reach PHY %s", __func__, pptr->path);
                return (0);
        }

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr);

        if (pwrk == NULL) {
                pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL, pmcs_nowrk, __func__);
                return (ENOMEM);
        }

        pwrk->arg = iomb;

        /*
         * If level > 0, we need to issue an SMP_REQUEST with a PHY_CONTROL
         * function to do either a link reset or hard reset.  If level == 0,
         * then we do a LOCAL_PHY_CONTROL IOMB to do link/hard reset to the
         * root (local) PHY
         */
        if (level) {
                stsoff = 2;
                iomb[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL,
                    PMCIN_SMP_REQUEST));
                iomb[1] = LE_32(pwrk->htag);
                iomb[2] = LE_32(pdevid);
                iomb[3] = LE_32(40 << SMP_REQUEST_LENGTH_SHIFT);
                /*
                 * Send SMP PHY CONTROL/HARD or LINK RESET
                 */
                iomb[4] = BE_32(0x40910000);
                iomb[5] = 0;

                if (type == PMCS_PHYOP_HARD_RESET) {
                        mbar = "SMP PHY CONTROL/HARD RESET";
                        iomb[6] = BE_32((phynum << 16) |
                            (PMCS_PHYOP_HARD_RESET << 8));
                } else {
                        mbar = "SMP PHY CONTROL/LINK RESET";
                        iomb[6] = BE_32((phynum << 16) |
                            (PMCS_PHYOP_LINK_RESET << 8));
                }
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: sending %s to %s for phy 0x%x",
                    __func__, mbar, pptr->parent->path, pptr->phynum);
                amt = 7;
        } else {
                /*
                 * Unlike most other Outbound messages, status for
                 * a local phy operation is in DWORD 3.
                 */
                stsoff = 3;
                iomb[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL,
                    PMCIN_LOCAL_PHY_CONTROL));
                iomb[1] = LE_32(pwrk->htag);
                if (type == PMCS_PHYOP_LINK_RESET) {
                        mbar = "LOCAL PHY LINK RESET";
                        iomb[2] = LE_32((PMCS_PHYOP_LINK_RESET << 8) | phynum);
                } else {
                        mbar = "LOCAL PHY HARD RESET";
                        iomb[2] = LE_32((PMCS_PHYOP_HARD_RESET << 8) | phynum);
                }
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: sending %s to %s", __func__, mbar, pptr->path);
                amt = 3;
        }

        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        msg = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (msg == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_pwork(pwp, pwrk);
                pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL, pmcs_nomsg, __func__);
                return (ENOMEM);
        }
        COPY_MESSAGE(msg, iomb, amt);
        htag = pwrk->htag;

        pmcs_hold_iport(pptr->iport);
        iport = pptr->iport;
        pmcs_smp_acquire(iport);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        pmcs_unlock_phy(pptr);
        WAIT_FOR(pwrk, 1000, result);
        pmcs_pwork(pwp, pwrk);
        pmcs_smp_release(iport);
        pmcs_rele_iport(iport);
        pmcs_lock_phy(pptr);
        if (result) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL, pmcs_timeo, __func__);

                if (pmcs_abort(pwp, pptr, htag, 0, 0)) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: Unable to issue SMP abort for htag 0x%08x",
                            __func__, htag);
                } else {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: Issuing SMP ABORT for htag 0x%08x",
                            __func__, htag);
                }
                return (EIO);
        }
        status = LE_32(iomb[stsoff]);

        if (status != PMCOUT_STATUS_OK) {
                char buf[32];
                const char *es =  pmcs_status_str(status);
                if (es == NULL) {
                        (void) snprintf(buf, sizeof (buf), "Status 0x%x",
                            status);
                        es = buf;
                }
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: %s action returned %s for %s", __func__, mbar, es,
                    pptr->path);
                return (status);
        }

        return (0);
}

/*
 * Stop the (real) phys.  No PHY or softstate locks are required as this only
 * happens during detach.
 */
void
pmcs_stop_phy(pmcs_hw_t *pwp, int phynum)
{
        int result;
        pmcs_phy_t *pptr;
        uint32_t *msg;
        struct pmcwork *pwrk;

        pptr =  pwp->root_phys + phynum;
        if (pptr == NULL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: unable to find port %d", __func__, phynum);
                return;
        }

        if (pwp->phys_started & (1 << phynum)) {
                pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr);

                if (pwrk == NULL) {
                        pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL,
                            pmcs_nowrk, __func__);
                        return;
                }

                mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                msg = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

                if (msg == NULL) {
                        mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                        pmcs_pwork(pwp, pwrk);
                        pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL,
                            pmcs_nomsg, __func__);
                        return;
                }

                msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_EVENTS, PMCIN_PHY_STOP));
                msg[1] = LE_32(pwrk->htag);
                msg[2] = LE_32(phynum);
                pwrk->state = PMCS_WORK_STATE_ONCHIP;
                /*
                 * Make this unconfigured now.
                 */
                INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
                WAIT_FOR(pwrk, 1000, result);
                pmcs_pwork(pwp, pwrk);
                if (result) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG,
                            pptr, NULL, pmcs_timeo, __func__);
                }

                pwp->phys_started &= ~(1 << phynum);
        }

        pptr->configured = 0;
}

/*
 * No locks should be required as this is only called during detach
 */
void
pmcs_stop_phys(pmcs_hw_t *pwp)
{
        int i;
        for (i = 0; i < pwp->nphy; i++) {
                if ((pwp->phyid_block_mask & (1 << i)) == 0) {
                        pmcs_stop_phy(pwp, i);
                }
        }
}

/*
 * Run SAS_DIAG_EXECUTE with cmd and cmd_desc passed.
 *      ERR_CNT_RESET: return status of cmd
 *      DIAG_REPORT_GET: return value of the counter
 */
int
pmcs_sas_diag_execute(pmcs_hw_t *pwp, uint32_t cmd, uint32_t cmd_desc,
    uint8_t phynum)
{
        uint32_t htag, *ptr, status, msg[PMCS_MSG_SIZE << 1];
        int result;
        struct pmcwork *pwrk;

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, NULL);
        if (pwrk == NULL) {
                pmcs_prt(pwp, PMCS_PRT_ERR, NULL, NULL, pmcs_nowrk, __func__);
                return (DDI_FAILURE);
        }
        pwrk->arg = msg;
        htag = pwrk->htag;
        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_EVENTS, PMCIN_SAS_DIAG_EXECUTE));
        msg[1] = LE_32(htag);
        msg[2] = LE_32((cmd << PMCS_DIAG_CMD_SHIFT) |
            (cmd_desc << PMCS_DIAG_CMD_DESC_SHIFT) | phynum);

        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (ptr == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_pwork(pwp, pwrk);
                pmcs_prt(pwp, PMCS_PRT_ERR, NULL, NULL, pmcs_nomsg, __func__);
                return (DDI_FAILURE);
        }
        COPY_MESSAGE(ptr, msg, 3);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        WAIT_FOR(pwrk, 1000, result);
        pmcs_pwork(pwp, pwrk);
        if (result) {
                pmcs_timed_out(pwp, htag, __func__);
                return (DDI_FAILURE);
        }

        status = LE_32(msg[3]);

        /* Return for counter reset */
        if (cmd == PMCS_ERR_CNT_RESET)
                return (status);

        /* Return for counter value */
        if (status) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: failed, status (0x%x)", __func__, status);
                return (DDI_FAILURE);
        }
        return (LE_32(msg[4]));
}

/* Get the current value of the counter for desc on phynum and return it. */
int
pmcs_get_diag_report(pmcs_hw_t *pwp, uint32_t desc, uint8_t phynum)
{
        return (pmcs_sas_diag_execute(pwp, PMCS_DIAG_REPORT_GET, desc, phynum));
}

/* Clear all of the counters for phynum. Returns the status of the command. */
int
pmcs_clear_diag_counters(pmcs_hw_t *pwp, uint8_t phynum)
{
        uint32_t        cmd = PMCS_ERR_CNT_RESET;
        uint32_t        cmd_desc;

        cmd_desc = PMCS_INVALID_DWORD_CNT;
        if (pmcs_sas_diag_execute(pwp, cmd, cmd_desc, phynum))
                return (DDI_FAILURE);

        cmd_desc = PMCS_DISPARITY_ERR_CNT;
        if (pmcs_sas_diag_execute(pwp, cmd, cmd_desc, phynum))
                return (DDI_FAILURE);

        cmd_desc = PMCS_LOST_DWORD_SYNC_CNT;
        if (pmcs_sas_diag_execute(pwp, cmd, cmd_desc, phynum))
                return (DDI_FAILURE);

        cmd_desc = PMCS_RESET_FAILED_CNT;
        if (pmcs_sas_diag_execute(pwp, cmd, cmd_desc, phynum))
                return (DDI_FAILURE);

        return (DDI_SUCCESS);
}

/*
 * Get firmware timestamp
 */
static int
pmcs_get_time_stamp(pmcs_hw_t *pwp, uint64_t *fw_ts, hrtime_t *sys_hr_ts)
{
        uint32_t htag, *ptr, msg[PMCS_MSG_SIZE << 1];
        int result;
        struct pmcwork *pwrk;

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, NULL);
        if (pwrk == NULL) {
                pmcs_prt(pwp, PMCS_PRT_ERR, NULL, NULL, pmcs_nowrk, __func__);
                return (-1);
        }
        pwrk->arg = msg;
        htag = pwrk->htag;
        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_EVENTS, PMCIN_GET_TIME_STAMP));
        msg[1] = LE_32(pwrk->htag);

        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (ptr == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_pwork(pwp, pwrk);
                pmcs_prt(pwp, PMCS_PRT_ERR, NULL, NULL, pmcs_nomsg, __func__);
                return (-1);
        }
        COPY_MESSAGE(ptr, msg, 2);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        WAIT_FOR(pwrk, 1000, result);
        pmcs_pwork(pwp, pwrk);
        if (result) {
                pmcs_timed_out(pwp, htag, __func__);
                return (-1);
        }

        mutex_enter(&pmcs_trace_lock);
        *sys_hr_ts = gethrtime();
        gethrestime(&pwp->sys_timestamp);
        *fw_ts = LE_32(msg[2]) | (((uint64_t)LE_32(msg[3])) << 32);
        mutex_exit(&pmcs_trace_lock);
        return (0);
}

/*
 * Dump all pertinent registers
 */

void
pmcs_register_dump(pmcs_hw_t *pwp)
{
        int i;
        uint32_t val;

        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "pmcs%d: Register dump start",
            ddi_get_instance(pwp->dip));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL,
            "OBDB (intr): 0x%08x (mask): 0x%08x (clear): 0x%08x",
            pmcs_rd_msgunit(pwp, PMCS_MSGU_OBDB),
            pmcs_rd_msgunit(pwp, PMCS_MSGU_OBDB_MASK),
            pmcs_rd_msgunit(pwp, PMCS_MSGU_OBDB_CLEAR));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "SCRATCH0: 0x%08x",
            pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH0));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "SCRATCH1: 0x%08x",
            pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH1));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "SCRATCH2: 0x%08x",
            pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH2));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "SCRATCH3: 0x%08x",
            pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH3));
        for (i = 0; i < PMCS_NIQ; i++) {
                pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "IQ %d: CI %u PI %u",
                    i, pmcs_rd_iqci(pwp, i), pmcs_rd_iqpi(pwp, i));
        }
        for (i = 0; i < PMCS_NOQ; i++) {
                pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "OQ %d: CI %u PI %u",
                    i, pmcs_rd_oqci(pwp, i), pmcs_rd_oqpi(pwp, i));
        }
        val = pmcs_rd_gst_tbl(pwp, PMCS_GST_BASE);
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL,
            "GST TABLE BASE: 0x%08x (STATE=0x%x QF=%d GSTLEN=%d HMI_ERR=0x%x)",
            val, PMCS_MPI_S(val), PMCS_QF(val), PMCS_GSTLEN(val) * 4,
            PMCS_HMI_ERR(val));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "GST TABLE IQFRZ0: 0x%08x",
            pmcs_rd_gst_tbl(pwp, PMCS_GST_IQFRZ0));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "GST TABLE IQFRZ1: 0x%08x",
            pmcs_rd_gst_tbl(pwp, PMCS_GST_IQFRZ1));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "GST TABLE MSGU TICK: 0x%08x",
            pmcs_rd_gst_tbl(pwp, PMCS_GST_MSGU_TICK));
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "GST TABLE IOP TICK: 0x%08x",
            pmcs_rd_gst_tbl(pwp, PMCS_GST_IOP_TICK));
        for (i = 0; i < pwp->nphy; i++) {
                uint32_t rerrf, pinfo, started = 0, link = 0;
                pinfo = pmcs_rd_gst_tbl(pwp, PMCS_GST_PHY_INFO(i));
                if (pinfo & 1) {
                        started = 1;
                        link = pinfo & 2;
                }
                rerrf = pmcs_rd_gst_tbl(pwp, PMCS_GST_RERR_INFO(i));
                pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL,
                    "GST TABLE PHY%d STARTED=%d LINK=%d RERR=0x%08x",
                    i, started, link, rerrf);
        }
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "pmcs%d: Register dump end",
            ddi_get_instance(pwp->dip));
}

/*
 * Handle SATA Abort and other error processing
 */
int
pmcs_abort_handler(pmcs_hw_t *pwp)
{
        pmcs_phy_t *pptr, *pnext, *pnext_uplevel[PMCS_MAX_XPND];
        pmcs_xscsi_t *tgt;
        int r, level = 0;

        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL, "%s", __func__);

        mutex_enter(&pwp->lock);
        pptr = pwp->root_phys;
        mutex_exit(&pwp->lock);

        while (pptr) {
                /*
                 * XXX: Need to make sure this doesn't happen
                 * XXX: when non-NCQ commands are running.
                 */
                pmcs_lock_phy(pptr);
                if (pptr->need_rl_ext) {
                        ASSERT(pptr->dtype == SATA);
                        if (pmcs_acquire_scratch(pwp, B_FALSE)) {
                                goto next_phy;
                        }
                        r = pmcs_sata_abort_ncq(pwp, pptr);
                        pmcs_release_scratch(pwp);
                        if (r == ENOMEM) {
                                goto next_phy;
                        }
                        if (r) {
                                r = pmcs_reset_phy(pwp, pptr,
                                    PMCS_PHYOP_LINK_RESET);
                                if (r == ENOMEM) {
                                        goto next_phy;
                                }
                                /* what if other failures happened? */
                                pptr->abort_pending = 1;
                                pptr->abort_sent = 0;
                        }
                }
                if (pptr->abort_pending == 0 || pptr->abort_sent) {
                        goto next_phy;
                }
                pptr->abort_pending = 0;
                if (pmcs_abort(pwp, pptr, pptr->device_id, 1, 1) == ENOMEM) {
                        pptr->abort_pending = 1;
                        goto next_phy;
                }
                pptr->abort_sent = 1;

                /*
                 * If the iport is no longer active, flush the queues
                 */
                if ((pptr->iport == NULL) ||
                    (pptr->iport->ua_state != UA_ACTIVE)) {
                        tgt = pptr->target;
                        if (tgt != NULL) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, tgt,
                                    "%s: Clearing target 0x%p, inactive iport",
                                    __func__, (void *) tgt);
                                mutex_enter(&tgt->statlock);
                                pmcs_clear_xp(pwp, tgt);
                                mutex_exit(&tgt->statlock);
                        }
                }

next_phy:
                if (pptr->children) {
                        pnext = pptr->children;
                        pnext_uplevel[level++] = pptr->sibling;
                } else {
                        pnext = pptr->sibling;
                        while ((pnext == NULL) && (level > 0)) {
                                pnext = pnext_uplevel[--level];
                        }
                }

                pmcs_unlock_phy(pptr);
                pptr = pnext;
        }

        return (0);
}

/*
 * Register a device (get a device handle for it).
 * Called with PHY lock held.
 */
int
pmcs_register_device(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        struct pmcwork *pwrk;
        int result = 0;
        uint32_t *msg;
        uint32_t tmp, status;
        uint32_t iomb[(PMCS_QENTRY_SIZE << 1) >> 2];

        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        msg = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        if (msg == NULL ||
            (pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr)) == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                result = ENOMEM;
                goto out;
        }

        pwrk->arg = iomb;
        pwrk->dtype = pptr->dtype;

        msg[1] = LE_32(pwrk->htag);
        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL, PMCIN_REGISTER_DEVICE));
        tmp = PMCS_DEVREG_TLR |
            (pptr->link_rate << PMCS_DEVREG_LINK_RATE_SHIFT);
        if (IS_ROOT_PHY(pptr)) {
                msg[2] = LE_32(pptr->portid |
                    (pptr->phynum << PMCS_PHYID_SHIFT));
        } else {
                msg[2] = LE_32(pptr->portid);
        }
        if (pptr->dtype == SATA) {
                if (IS_ROOT_PHY(pptr)) {
                        tmp |= PMCS_DEVREG_TYPE_SATA_DIRECT;
                } else {
                        tmp |= PMCS_DEVREG_TYPE_SATA;
                }
        } else {
                tmp |= PMCS_DEVREG_TYPE_SAS;
        }
        msg[3] = LE_32(tmp);
        msg[4] = LE_32(PMCS_DEVREG_IT_NEXUS_TIMEOUT);
        (void) memcpy(&msg[5], pptr->sas_address, 8);

        CLEAN_MESSAGE(msg, 7);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        pmcs_unlock_phy(pptr);
        WAIT_FOR(pwrk, 250, result);
        pmcs_pwork(pwp, pwrk);
        pmcs_lock_phy(pptr);

        if (result) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL, pmcs_timeo, __func__);
                result = ETIMEDOUT;
                goto out;
        }
        status = LE_32(iomb[2]);
        tmp = LE_32(iomb[3]);
        switch (status) {
        case PMCS_DEVREG_OK:
        case PMCS_DEVREG_DEVICE_ALREADY_REGISTERED:
        case PMCS_DEVREG_PHY_ALREADY_REGISTERED:
                if (pmcs_validate_devid(pwp->root_phys, pptr, tmp) == B_FALSE) {
                        result = EEXIST;
                        goto out;
                } else if (status != PMCS_DEVREG_OK) {
                        if (tmp == 0xffffffff) {        /* F/W bug */
                                pmcs_prt(pwp, PMCS_PRT_INFO, pptr, NULL,
                                    "%s: phy %s already has bogus devid 0x%x",
                                    __func__, pptr->path, tmp);
                                result = EIO;
                                goto out;
                        } else {
                                pmcs_prt(pwp, PMCS_PRT_INFO, pptr, NULL,
                                    "%s: phy %s already has a device id 0x%x",
                                    __func__, pptr->path, tmp);
                        }
                }
                break;
        default:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: status 0x%x when trying to register device %s",
                    __func__, status, pptr->path);
                result = EIO;
                goto out;
        }
        pptr->device_id = tmp;
        pptr->valid_device_id = 1;
        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL, "Phy %s/" SAS_ADDR_FMT
            " registered with device_id 0x%x (portid %d)", pptr->path,
            SAS_ADDR_PRT(pptr->sas_address), tmp, pptr->portid);
out:
        return (result);
}

/*
 * Deregister a device (remove a device handle).
 * Called with PHY locked.
 */
void
pmcs_deregister_device(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        struct pmcwork *pwrk;
        uint32_t msg[PMCS_MSG_SIZE], *ptr, status;
        uint32_t iomb[(PMCS_QENTRY_SIZE << 1) >> 2];
        int result;

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr);
        if (pwrk == NULL) {
                return;
        }

        pwrk->arg = iomb;
        pwrk->dtype = pptr->dtype;
        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (ptr == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_pwork(pwp, pwrk);
                return;
        }
        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL,
            PMCIN_DEREGISTER_DEVICE_HANDLE));
        msg[1] = LE_32(pwrk->htag);
        msg[2] = LE_32(pptr->device_id);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        COPY_MESSAGE(ptr, msg, 3);
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        pmcs_unlock_phy(pptr);
        WAIT_FOR(pwrk, 250, result);
        pmcs_pwork(pwp, pwrk);
        pmcs_lock_phy(pptr);

        if (result) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL, pmcs_timeo, __func__);
                return;
        }
        status = LE_32(iomb[2]);
        if (status != PMCOUT_STATUS_OK) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: status 0x%x when trying to deregister device %s",
                    __func__, status, pptr->path);
        } else {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: device %s deregistered", __func__, pptr->path);
        }

        pptr->device_id = PMCS_INVALID_DEVICE_ID;
        pptr->configured = 0;
        pptr->deregister_wait = 0;
        pptr->valid_device_id = 0;
}

/*
 * Deregister all registered devices.
 */
void
pmcs_deregister_devices(pmcs_hw_t *pwp, pmcs_phy_t *phyp)
{
        /*
         * Start at the maximum level and walk back to level 0.  This only
         * gets done during detach after all threads and timers have been
         * destroyed.
         */
        while (phyp) {
                if (phyp->children) {
                        pmcs_deregister_devices(pwp, phyp->children);
                }
                pmcs_lock_phy(phyp);
                if (phyp->valid_device_id) {
                        pmcs_deregister_device(pwp, phyp);
                }
                pmcs_unlock_phy(phyp);
                phyp = phyp->sibling;
        }
}

/*
 * Perform a 'soft' reset on the PMC chip
 */
int
pmcs_soft_reset(pmcs_hw_t *pwp, boolean_t no_restart)
{
        uint32_t s2, sfrbits, gsm, rapchk, wapchk, wdpchk, spc, tsmode;
        pmcs_phy_t *pptr;
        char *msg = NULL;
        int i;

        /*
         * Disable interrupts
         */
        pmcs_wr_msgunit(pwp, PMCS_MSGU_OBDB_MASK, 0xffffffff);
        pmcs_wr_msgunit(pwp, PMCS_MSGU_OBDB_CLEAR, 0xffffffff);

        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL, "%s", __func__);

        if (pwp->locks_initted) {
                mutex_enter(&pwp->lock);
        }
        pwp->blocked = 1;

        /*
         * Clear our softstate copies of the MSGU and IOP heartbeats.
         */
        pwp->last_msgu_tick = pwp->last_iop_tick = 0;

        /*
         * Step 1
         */
        s2 = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH2);
        if ((s2 & PMCS_MSGU_HOST_SOFT_RESET_READY) == 0) {
                pmcs_wr_gsm_reg(pwp, RB6_ACCESS, RB6_NMI_SIGNATURE);
                pmcs_wr_gsm_reg(pwp, RB6_ACCESS, RB6_NMI_SIGNATURE);
                for (i = 0; i < 100; i++) {
                        s2 = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH2) &
                            PMCS_MSGU_HOST_SOFT_RESET_READY;
                        if (s2) {
                                break;
                        }
                        drv_usecwait(10000);
                }
                s2 = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH2) &
                    PMCS_MSGU_HOST_SOFT_RESET_READY;
                if (s2 == 0) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                            "%s: PMCS_MSGU_HOST_SOFT_RESET_READY never came "
                            "ready", __func__);
                        pmcs_register_dump(pwp);
                        if ((pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH1) &
                            PMCS_MSGU_CPU_SOFT_RESET_READY) == 0 ||
                            (pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH2) &
                            PMCS_MSGU_CPU_SOFT_RESET_READY) == 0) {
                                pwp->state = STATE_DEAD;
                                pwp->blocked = 0;
                                if (pwp->locks_initted) {
                                        mutex_exit(&pwp->lock);
                                }
                                return (-1);
                        }
                }
        }

        /*
         * Step 2
         */
        pmcs_wr_gsm_reg(pwp, NMI_EN_VPE0_IOP, 0);
        drv_usecwait(10);
        pmcs_wr_gsm_reg(pwp, NMI_EN_VPE0_AAP1, 0);
        drv_usecwait(10);
        pmcs_wr_topunit(pwp, PMCS_EVENT_INT_ENABLE, 0);
        drv_usecwait(10);
        pmcs_wr_topunit(pwp, PMCS_EVENT_INT_STAT,
            pmcs_rd_topunit(pwp, PMCS_EVENT_INT_STAT));
        drv_usecwait(10);
        pmcs_wr_topunit(pwp, PMCS_ERROR_INT_ENABLE, 0);
        drv_usecwait(10);
        pmcs_wr_topunit(pwp, PMCS_ERROR_INT_STAT,
            pmcs_rd_topunit(pwp, PMCS_ERROR_INT_STAT));
        drv_usecwait(10);

        sfrbits = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH1) &
            PMCS_MSGU_AAP_SFR_PROGRESS;
        sfrbits ^= PMCS_MSGU_AAP_SFR_PROGRESS;
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "PMCS_MSGU_HOST_SCRATCH0 "
            "%08x -> %08x", pmcs_rd_msgunit(pwp, PMCS_MSGU_HOST_SCRATCH0),
            HST_SFT_RESET_SIG);
        pmcs_wr_msgunit(pwp, PMCS_MSGU_HOST_SCRATCH0, HST_SFT_RESET_SIG);

        /*
         * Step 3
         */
        gsm = pmcs_rd_gsm_reg(pwp, 0, GSM_CFG_AND_RESET);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "GSM %08x -> %08x", gsm,
            gsm & ~PMCS_SOFT_RESET_BITS);
        pmcs_wr_gsm_reg(pwp, GSM_CFG_AND_RESET, gsm & ~PMCS_SOFT_RESET_BITS);

        /*
         * Step 4
         */
        rapchk = pmcs_rd_gsm_reg(pwp, 0, READ_ADR_PARITY_CHK_EN);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "READ_ADR_PARITY_CHK_EN "
            "%08x -> %08x", rapchk, 0);
        pmcs_wr_gsm_reg(pwp, READ_ADR_PARITY_CHK_EN, 0);
        wapchk = pmcs_rd_gsm_reg(pwp, 0, WRITE_ADR_PARITY_CHK_EN);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "WRITE_ADR_PARITY_CHK_EN "
            "%08x -> %08x", wapchk, 0);
        pmcs_wr_gsm_reg(pwp, WRITE_ADR_PARITY_CHK_EN, 0);
        wdpchk = pmcs_rd_gsm_reg(pwp, 0, WRITE_DATA_PARITY_CHK_EN);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "WRITE_DATA_PARITY_CHK_EN "
            "%08x -> %08x", wdpchk, 0);
        pmcs_wr_gsm_reg(pwp, WRITE_DATA_PARITY_CHK_EN, 0);

        /*
         * Step 5
         */
        drv_usecwait(100);

        /*
         * Step 5.5 (Temporary workaround for 1.07.xx Beta)
         */
        tsmode = pmcs_rd_gsm_reg(pwp, 0, PMCS_GPIO_TRISTATE_MODE_ADDR);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "GPIO TSMODE %08x -> %08x",
            tsmode, tsmode & ~(PMCS_GPIO_TSMODE_BIT0|PMCS_GPIO_TSMODE_BIT1));
        pmcs_wr_gsm_reg(pwp, PMCS_GPIO_TRISTATE_MODE_ADDR,
            tsmode & ~(PMCS_GPIO_TSMODE_BIT0|PMCS_GPIO_TSMODE_BIT1));
        drv_usecwait(10);

        /*
         * Step 6
         */
        spc = pmcs_rd_topunit(pwp, PMCS_SPC_RESET);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "SPC_RESET %08x -> %08x",
            spc, spc & ~(PCS_IOP_SS_RSTB|PCS_AAP1_SS_RSTB));
        pmcs_wr_topunit(pwp, PMCS_SPC_RESET,
            spc & ~(PCS_IOP_SS_RSTB|PCS_AAP1_SS_RSTB));
        drv_usecwait(10);

        /*
         * Step 7
         */
        spc = pmcs_rd_topunit(pwp, PMCS_SPC_RESET);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "SPC_RESET %08x -> %08x",
            spc, spc & ~(BDMA_CORE_RSTB|OSSP_RSTB));
        pmcs_wr_topunit(pwp, PMCS_SPC_RESET, spc & ~(BDMA_CORE_RSTB|OSSP_RSTB));

        /*
         * Step 8
         */
        drv_usecwait(100);

        /*
         * Step 9
         */
        spc = pmcs_rd_topunit(pwp, PMCS_SPC_RESET);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "SPC_RESET %08x -> %08x",
            spc, spc | (BDMA_CORE_RSTB|OSSP_RSTB));
        pmcs_wr_topunit(pwp, PMCS_SPC_RESET, spc | (BDMA_CORE_RSTB|OSSP_RSTB));

        /*
         * Step 10
         */
        drv_usecwait(100);

        /*
         * Step 11
         */
        gsm = pmcs_rd_gsm_reg(pwp, 0, GSM_CFG_AND_RESET);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "GSM %08x -> %08x", gsm,
            gsm | PMCS_SOFT_RESET_BITS);
        pmcs_wr_gsm_reg(pwp, GSM_CFG_AND_RESET, gsm | PMCS_SOFT_RESET_BITS);
        drv_usecwait(10);

        /*
         * Step 12
         */
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "READ_ADR_PARITY_CHK_EN "
            "%08x -> %08x", pmcs_rd_gsm_reg(pwp, 0, READ_ADR_PARITY_CHK_EN),
            rapchk);
        pmcs_wr_gsm_reg(pwp, READ_ADR_PARITY_CHK_EN, rapchk);
        drv_usecwait(10);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "WRITE_ADR_PARITY_CHK_EN "
            "%08x -> %08x", pmcs_rd_gsm_reg(pwp, 0, WRITE_ADR_PARITY_CHK_EN),
            wapchk);
        pmcs_wr_gsm_reg(pwp, WRITE_ADR_PARITY_CHK_EN, wapchk);
        drv_usecwait(10);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "WRITE_DATA_PARITY_CHK_EN "
            "%08x -> %08x", pmcs_rd_gsm_reg(pwp, 0, WRITE_DATA_PARITY_CHK_EN),
            wapchk);
        pmcs_wr_gsm_reg(pwp, WRITE_DATA_PARITY_CHK_EN, wdpchk);
        drv_usecwait(10);

        /*
         * Step 13
         */
        spc = pmcs_rd_topunit(pwp, PMCS_SPC_RESET);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL, "SPC_RESET %08x -> %08x",
            spc, spc | (PCS_IOP_SS_RSTB|PCS_AAP1_SS_RSTB));
        pmcs_wr_topunit(pwp, PMCS_SPC_RESET,
            spc | (PCS_IOP_SS_RSTB|PCS_AAP1_SS_RSTB));

        /*
         * Step 14
         */
        drv_usecwait(100);

        /*
         * Step 15
         */
        for (spc = 0, i = 0; i < 1000; i++) {
                drv_usecwait(1000);
                spc = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH1);
                if ((spc & PMCS_MSGU_AAP_SFR_PROGRESS) == sfrbits) {
                        break;
                }
        }

        if ((spc & PMCS_MSGU_AAP_SFR_PROGRESS) != sfrbits) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "SFR didn't toggle (sfr 0x%x)", spc);
                pwp->state = STATE_DEAD;
                pwp->blocked = 0;
                if (pwp->locks_initted) {
                        mutex_exit(&pwp->lock);
                }
                return (-1);
        }

        /*
         * Step 16
         */
        pmcs_wr_msgunit(pwp, PMCS_MSGU_OBDB_MASK, 0xffffffff);
        pmcs_wr_msgunit(pwp, PMCS_MSGU_OBDB_CLEAR, 0xffffffff);

        /*
         * Wait for up to 5 seconds for AAP state to come either ready or error.
         */
        for (i = 0; i < 50; i++) {
                spc = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH1) &
                    PMCS_MSGU_AAP_STATE_MASK;
                if (spc == PMCS_MSGU_AAP_STATE_ERROR ||
                    spc == PMCS_MSGU_AAP_STATE_READY) {
                        break;
                }
                drv_usecwait(100000);
        }
        spc = pmcs_rd_msgunit(pwp, PMCS_MSGU_SCRATCH1);
        if ((spc & PMCS_MSGU_AAP_STATE_MASK) != PMCS_MSGU_AAP_STATE_READY) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "soft reset failed (state 0x%x)", spc);
                pwp->state = STATE_DEAD;
                pwp->blocked = 0;
                if (pwp->locks_initted) {
                        mutex_exit(&pwp->lock);
                }
                return (-1);
        }

        /* Clear the firmware log */
        if (pwp->fwlogp) {
                bzero(pwp->fwlogp, PMCS_FWLOG_SIZE);
        }

        /* Reset our queue indices and entries */
        bzero(pwp->shadow_iqpi, sizeof (pwp->shadow_iqpi));
        bzero(pwp->last_iqci, sizeof (pwp->last_iqci));
        bzero(pwp->last_htag, sizeof (pwp->last_htag));
        for (i = 0; i < PMCS_NIQ; i++) {
                if (pwp->iqp[i]) {
                        bzero(pwp->iqp[i], PMCS_QENTRY_SIZE * pwp->ioq_depth);
                        pmcs_wr_iqpi(pwp, i, 0);
                        pmcs_wr_iqci(pwp, i, 0);
                }
        }
        for (i = 0; i < PMCS_NOQ; i++) {
                if (pwp->oqp[i]) {
                        bzero(pwp->oqp[i], PMCS_QENTRY_SIZE * pwp->ioq_depth);
                        pmcs_wr_oqpi(pwp, i, 0);
                        pmcs_wr_oqci(pwp, i, 0);
                }

        }

        if (pwp->state == STATE_DEAD || pwp->state == STATE_UNPROBING ||
            pwp->state == STATE_PROBING || pwp->locks_initted == 0) {
                pwp->blocked = 0;
                if (pwp->locks_initted) {
                        mutex_exit(&pwp->lock);
                }
                return (0);
        }

        /*
         * Return at this point if we dont need to startup.
         */
        if (no_restart) {
                return (0);
        }

        ASSERT(pwp->locks_initted != 0);

        /*
         * Flush the target queues and clear each target's PHY
         */
        if (pwp->targets) {
                for (i = 0; i < pwp->max_dev; i++) {
                        pmcs_xscsi_t *xp = pwp->targets[i];

                        if (xp == NULL) {
                                continue;
                        }

                        mutex_enter(&xp->statlock);
                        pmcs_flush_target_queues(pwp, xp, PMCS_TGT_ALL_QUEUES);
                        xp->phy = NULL;
                        mutex_exit(&xp->statlock);
                }
        }

        /*
         * Zero out the ports list, free non root phys, clear root phys
         */
        bzero(pwp->ports, sizeof (pwp->ports));
        pmcs_free_all_phys(pwp, pwp->root_phys);
        for (pptr = pwp->root_phys; pptr; pptr = pptr->sibling) {
                pmcs_lock_phy(pptr);
                pmcs_clear_phy(pwp, pptr);
                pptr->target = NULL;
                pmcs_unlock_phy(pptr);
        }

        /*
         * Restore Interrupt Mask
         */
        pmcs_wr_msgunit(pwp, PMCS_MSGU_OBDB_MASK, pwp->intr_mask);
        pmcs_wr_msgunit(pwp, PMCS_MSGU_OBDB_CLEAR, 0xffffffff);

        pwp->mpi_table_setup = 0;
        mutex_exit(&pwp->lock);

        /*
         * Set up MPI again.
         */
        if (pmcs_setup(pwp)) {
                msg = "unable to setup MPI tables again";
                goto fail_restart;
        }
        pmcs_report_fwversion(pwp);

        /*
         * Restart MPI
         */
        if (pmcs_start_mpi(pwp)) {
                msg = "unable to restart MPI again";
                goto fail_restart;
        }

        mutex_enter(&pwp->lock);
        SCHEDULE_WORK(pwp, PMCS_WORK_RUN_QUEUES);
        mutex_exit(&pwp->lock);

        /*
         * Run any completions
         */
        PMCS_CQ_RUN(pwp);

        /*
         * Delay
         */
        drv_usecwait(1000000);
        return (0);

fail_restart:
        mutex_enter(&pwp->lock);
        pwp->state = STATE_DEAD;
        mutex_exit(&pwp->lock);
        pmcs_prt(pwp, PMCS_PRT_ERR, NULL, NULL,
            "%s: Failed: %s", __func__, msg);
        return (-1);
}


/*
 * Perform a 'hot' reset, which will soft reset the chip and
 * restore the state back to pre-reset context. Called with pwp
 * lock held.
 */
int
pmcs_hot_reset(pmcs_hw_t *pwp)
{
        pmcs_iport_t    *iport;

        ASSERT(mutex_owned(&pwp->lock));
        pwp->state = STATE_IN_RESET;

        /*
         * For any iports on this HBA, report empty target sets and
         * then tear them down.
         */
        rw_enter(&pwp->iports_lock, RW_READER);
        for (iport = list_head(&pwp->iports); iport != NULL;
            iport = list_next(&pwp->iports, iport)) {
                mutex_enter(&iport->lock);
                (void) scsi_hba_tgtmap_set_begin(iport->iss_tgtmap);
                (void) scsi_hba_tgtmap_set_end(iport->iss_tgtmap, 0);
                pmcs_iport_teardown_phys(iport);
                mutex_exit(&iport->lock);
        }
        rw_exit(&pwp->iports_lock);

        /* Grab a register dump, in the event that reset fails */
        pmcs_register_dump_int(pwp);
        mutex_exit(&pwp->lock);

        /* Ensure discovery is not running before we proceed */
        mutex_enter(&pwp->config_lock);
        while (pwp->configuring) {
                cv_wait(&pwp->config_cv, &pwp->config_lock);
        }
        mutex_exit(&pwp->config_lock);

        /* Issue soft reset and clean up related softstate */
        if (pmcs_soft_reset(pwp, B_FALSE)) {
                /*
                 * Disable interrupts, in case we got far enough along to
                 * enable them, then fire off ereport and service impact.
                 */
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: failed soft reset", __func__);
                pmcs_wr_msgunit(pwp, PMCS_MSGU_OBDB_MASK, 0xffffffff);
                pmcs_wr_msgunit(pwp, PMCS_MSGU_OBDB_CLEAR, 0xffffffff);
                pmcs_fm_ereport(pwp, DDI_FM_DEVICE_NO_RESPONSE);
                ddi_fm_service_impact(pwp->dip, DDI_SERVICE_LOST);
                mutex_enter(&pwp->lock);
                pwp->state = STATE_DEAD;
                return (DDI_FAILURE);
        }

        mutex_enter(&pwp->lock);
        pwp->state = STATE_RUNNING;
        mutex_exit(&pwp->lock);

        /*
         * Finally, restart the phys, which will bring the iports back
         * up and eventually result in discovery running.
         */
        if (pmcs_start_phys(pwp)) {
                /* We should be up and running now, so retry */
                if (pmcs_start_phys(pwp)) {
                        /* Apparently unable to restart PHYs, fail */
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                            "%s: failed to restart PHYs after soft reset",
                            __func__);
                        mutex_enter(&pwp->lock);
                        return (DDI_FAILURE);
                }
        }

        mutex_enter(&pwp->lock);
        return (DDI_SUCCESS);
}

/*
 * Reset a device or a logical unit.
 */
int
pmcs_reset_dev(pmcs_hw_t *pwp, pmcs_phy_t *pptr, uint64_t lun)
{
        int rval = 0;

        if (pptr == NULL) {
                return (ENXIO);
        }

        pmcs_lock_phy(pptr);
        if (pptr->dtype == SAS) {
                /*
                 * Some devices do not support SAS_I_T_NEXUS_RESET as
                 * it is not a mandatory (in SAM4) task management
                 * function, while LOGIC_UNIT_RESET is mandatory.
                 *
                 * The problem here is that we need to iterate over
                 * all known LUNs to emulate the semantics of
                 * "RESET_TARGET".
                 *
                 * XXX: FIX ME
                 */
                if (lun == (uint64_t)-1) {
                        lun = 0;
                }
                rval = pmcs_ssp_tmf(pwp, pptr, SAS_LOGICAL_UNIT_RESET, 0, lun,
                    NULL);
        } else if (pptr->dtype == SATA) {
                if (lun != 0ull) {
                        pmcs_unlock_phy(pptr);
                        return (EINVAL);
                }
                rval = pmcs_reset_phy(pwp, pptr, PMCS_PHYOP_LINK_RESET);
        } else {
                pmcs_unlock_phy(pptr);
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: cannot reset a SMP device yet (%s)",
                    __func__, pptr->path);
                return (EINVAL);
        }

        /*
         * Now harvest any commands killed by this action
         * by issuing an ABORT for all commands on this device.
         *
         * We do this even if the the tmf or reset fails (in case there
         * are any dead commands around to be harvested *anyway*).
         * We don't have to await for the abort to complete.
         */
        if (pmcs_abort(pwp, pptr, 0, 1, 0)) {
                pptr->abort_pending = 1;
                SCHEDULE_WORK(pwp, PMCS_WORK_ABORT_HANDLE);
        }

        pmcs_unlock_phy(pptr);
        return (rval);
}

/*
 * Called with PHY locked.
 */
static int
pmcs_get_device_handle(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        if (pptr->valid_device_id == 0) {
                int result = pmcs_register_device(pwp, pptr);

                /*
                 * If we changed while registering, punt
                 */
                if (pptr->changed) {
                        RESTART_DISCOVERY(pwp);
                        return (-1);
                }

                /*
                 * If we had a failure to register, check against errors.
                 * An ENOMEM error means we just retry (temp resource shortage).
                 */
                if (result == ENOMEM) {
                        PHY_CHANGED(pwp, pptr);
                        RESTART_DISCOVERY(pwp);
                        return (-1);
                }

                /*
                 * An ETIMEDOUT error means we retry (if our counter isn't
                 * exhausted)
                 */
                if (result == ETIMEDOUT) {
                        if (ddi_get_lbolt() < pptr->config_stop) {
                                PHY_CHANGED(pwp, pptr);
                                RESTART_DISCOVERY(pwp);
                        } else {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                                    "%s: Retries exhausted for %s, killing",
                                    __func__, pptr->path);
                                pptr->config_stop = 0;
                                pmcs_kill_changed(pwp, pptr, 0);
                        }
                        return (-1);
                }
                /*
                 * Other errors or no valid device id is fatal, but don't
                 * preclude a future action.
                 */
                if (result || pptr->valid_device_id == 0) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: %s could not be registered", __func__,
                            pptr->path);
                        return (-1);
                }
        }
        return (0);
}

int
pmcs_iport_tgtmap_create(pmcs_iport_t *iport)
{
        ASSERT(iport);
        if (iport == NULL)
                return (B_FALSE);

        pmcs_prt(iport->pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL, "%s", __func__);

        /* create target map */
        if (scsi_hba_tgtmap_create(iport->dip, SCSI_TM_FULLSET,
            tgtmap_csync_usec, tgtmap_stable_usec, (void *)iport,
            pmcs_tgtmap_activate_cb, pmcs_tgtmap_deactivate_cb,
            &iport->iss_tgtmap) != DDI_SUCCESS) {
                pmcs_prt(iport->pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: failed to create tgtmap", __func__);
                return (B_FALSE);
        }
        return (B_TRUE);
}

int
pmcs_iport_tgtmap_destroy(pmcs_iport_t *iport)
{
        ASSERT(iport && iport->iss_tgtmap);
        if ((iport == NULL) || (iport->iss_tgtmap == NULL))
                return (B_FALSE);

        pmcs_prt(iport->pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL, "%s", __func__);

        /* destroy target map */
        scsi_hba_tgtmap_destroy(iport->iss_tgtmap);
        return (B_TRUE);
}

/*
 * Remove all phys from an iport's phymap and empty it's phylist.
 * Called when a port has been reset by the host (see pmcs_intr.c)
 * or prior to issuing a soft reset if we detect a stall on the chip
 * (see pmcs_attach.c).
 */
void
pmcs_iport_teardown_phys(pmcs_iport_t *iport)
{
        pmcs_hw_t               *pwp;
        sas_phymap_phys_t       *phys;
        int                     phynum;

        ASSERT(iport);
        ASSERT(mutex_owned(&iport->lock));
        pwp = iport->pwp;
        ASSERT(pwp);

        /*
         * Remove all phys from the iport handle's phy list, unset its
         * primary phy and update its state.
         */
        pmcs_remove_phy_from_iport(iport, NULL);
        iport->pptr = NULL;
        iport->ua_state = UA_PEND_DEACTIVATE;

        /* Remove all phys from the phymap */
        phys = sas_phymap_ua2phys(pwp->hss_phymap, iport->ua);
        if (phys) {
                while ((phynum = sas_phymap_phys_next(phys)) != -1) {
                        (void) sas_phymap_phy_rem(pwp->hss_phymap, phynum);
                }
                sas_phymap_phys_free(phys);
        }
}

/*
 * Query the phymap and populate the iport handle passed in.
 * Called with iport lock held.
 */
int
pmcs_iport_configure_phys(pmcs_iport_t *iport)
{
        pmcs_hw_t               *pwp;
        pmcs_phy_t              *pptr;
        sas_phymap_phys_t       *phys;
        int                     phynum;
        int                     inst;

        ASSERT(iport);
        ASSERT(mutex_owned(&iport->lock));
        pwp = iport->pwp;
        ASSERT(pwp);
        inst = ddi_get_instance(iport->dip);

        mutex_enter(&pwp->lock);
        ASSERT(pwp->root_phys != NULL);

        /*
         * Query the phymap regarding the phys in this iport and populate
         * the iport's phys list. Hereafter this list is maintained via
         * port up and down events in pmcs_intr.c
         */
        ASSERT(list_is_empty(&iport->phys));
        phys = sas_phymap_ua2phys(pwp->hss_phymap, iport->ua);
        ASSERT(phys != NULL);
        while ((phynum = sas_phymap_phys_next(phys)) != -1) {
                /* Grab the phy pointer from root_phys */
                pptr = pwp->root_phys + phynum;
                ASSERT(pptr);
                pmcs_lock_phy(pptr);
                ASSERT(pptr->phynum == phynum);

                /*
                 * Set a back pointer in the phy to this iport.
                 */
                pptr->iport = iport;

                /*
                 * If this phy is the primary, set a pointer to it on our
                 * iport handle, and set our portid from it.
                 */
                if (!pptr->subsidiary) {
                        iport->pptr = pptr;
                        iport->portid = pptr->portid;
                }

                /*
                 * Finally, insert the phy into our list
                 */
                pmcs_unlock_phy(pptr);
                pmcs_add_phy_to_iport(iport, pptr);

                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL, "%s: found "
                    "phy %d [0x%p] on iport%d, refcnt(%d)", __func__, phynum,
                    (void *)pptr, inst, iport->refcnt);
        }
        mutex_exit(&pwp->lock);
        sas_phymap_phys_free(phys);
        RESTART_DISCOVERY(pwp);
        return (DDI_SUCCESS);
}

/*
 * Return the iport that ua is associated with, or NULL.  If an iport is
 * returned, it will be held and the caller must release the hold.
 */
static pmcs_iport_t *
pmcs_get_iport_by_ua(pmcs_hw_t *pwp, char *ua)
{
        pmcs_iport_t    *iport = NULL;

        rw_enter(&pwp->iports_lock, RW_READER);
        for (iport = list_head(&pwp->iports);
            iport != NULL;
            iport = list_next(&pwp->iports, iport)) {
                mutex_enter(&iport->lock);
                if (strcmp(iport->ua, ua) == 0) {
                        mutex_exit(&iport->lock);
                        pmcs_hold_iport(iport);
                        break;
                }
                mutex_exit(&iport->lock);
        }
        rw_exit(&pwp->iports_lock);

        return (iport);
}

/*
 * Return the iport that pptr is associated with, or NULL.
 * If an iport is returned, there is a hold that the caller must release.
 */
pmcs_iport_t *
pmcs_get_iport_by_wwn(pmcs_hw_t *pwp, uint64_t wwn)
{
        pmcs_iport_t    *iport = NULL;
        char            *ua;

        ua = sas_phymap_lookup_ua(pwp->hss_phymap, pwp->sas_wwns[0], wwn);
        if (ua) {
                iport = pmcs_get_iport_by_ua(pwp, ua);
                if (iport) {
                        mutex_enter(&iport->lock);
                        pmcs_iport_active(iport);
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL, "%s: "
                            "found iport [0x%p] on ua (%s), refcnt (%d)",
                            __func__, (void *)iport, ua, iport->refcnt);
                        mutex_exit(&iport->lock);
                }
        }

        return (iport);
}

/*
 * Promote the next phy on this port to primary, and return it.
 * Called when the primary PHY on a port is going down, but the port
 * remains up (see pmcs_intr.c).
 */
pmcs_phy_t *
pmcs_promote_next_phy(pmcs_phy_t *prev_primary)
{
        pmcs_hw_t       *pwp;
        pmcs_iport_t    *iport;
        pmcs_phy_t      *pptr, *child;
        int             portid;

        pmcs_lock_phy(prev_primary);
        portid = prev_primary->portid;
        iport  = prev_primary->iport;
        pwp    = prev_primary->pwp;

        /* Use the first available phy in this port */
        for (pptr = pwp->root_phys; pptr; pptr = pptr->sibling) {
                if ((pptr->portid == portid) && (pptr != prev_primary)) {
                        mutex_enter(&pptr->phy_lock);
                        break;
                }
        }

        if (pptr == NULL) {
                pmcs_unlock_phy(prev_primary);
                return (NULL);
        }

        if (iport) {
                mutex_enter(&iport->lock);
                iport->pptr = pptr;
                mutex_exit(&iport->lock);
        }

        /* Update the phy handle with the data from the previous primary */
        pptr->children          = prev_primary->children;
        child = pptr->children;
        while (child) {
                child->parent = pptr;
                child = child->sibling;
        }
        pptr->ncphy             = prev_primary->ncphy;
        pptr->width             = prev_primary->width;
        pptr->dtype             = prev_primary->dtype;
        pptr->pend_dtype        = prev_primary->pend_dtype;
        pptr->tolerates_sas2    = prev_primary->tolerates_sas2;
        pptr->atdt              = prev_primary->atdt;
        pptr->portid            = prev_primary->portid;
        pptr->link_rate         = prev_primary->link_rate;
        pptr->configured        = prev_primary->configured;
        pptr->iport             = prev_primary->iport;
        pptr->target            = prev_primary->target;
        if (pptr->target) {
                pptr->target->phy = pptr;
        }

        /* Update the phy mask properties for the affected PHYs */
        /* Clear the current values... */
        pmcs_update_phy_pm_props(pptr, pptr->att_port_pm_tmp,
            pptr->tgt_port_pm_tmp, B_FALSE);
        /* ...replace with the values from prev_primary... */
        pmcs_update_phy_pm_props(pptr, prev_primary->att_port_pm_tmp,
            prev_primary->tgt_port_pm_tmp, B_TRUE);
        /* ...then clear prev_primary's PHY values from the new primary */
        pmcs_update_phy_pm_props(pptr, prev_primary->att_port_pm,
            prev_primary->tgt_port_pm, B_FALSE);
        /* Clear the prev_primary's values */
        pmcs_update_phy_pm_props(prev_primary, prev_primary->att_port_pm_tmp,
            prev_primary->tgt_port_pm_tmp, B_FALSE);

        pptr->subsidiary = 0;

        prev_primary->subsidiary = 1;
        prev_primary->children = NULL;
        prev_primary->target = NULL;
        pptr->device_id = prev_primary->device_id;
        pptr->valid_device_id = prev_primary->valid_device_id;
        pmcs_unlock_phy(prev_primary);

        /*
         * We call pmcs_unlock_phy() on pptr because it now contains the
         * list of children.
         */
        pmcs_unlock_phy(pptr);

        return (pptr);
}

void
pmcs_hold_iport(pmcs_iport_t *iport)
{
        /*
         * Grab a reference to this iport.
         */
        ASSERT(iport);
        mutex_enter(&iport->refcnt_lock);
        iport->refcnt++;
        mutex_exit(&iport->refcnt_lock);

        pmcs_prt(iport->pwp, PMCS_PRT_DEBUG2, NULL, NULL, "%s: iport "
            "[0x%p] refcnt (%d)", __func__, (void *)iport, iport->refcnt);
}

void
pmcs_rele_iport(pmcs_iport_t *iport)
{
        /*
         * Release a refcnt on this iport. If this is the last reference,
         * signal the potential waiter in pmcs_iport_unattach().
         */
        ASSERT(iport->refcnt > 0);
        mutex_enter(&iport->refcnt_lock);
        iport->refcnt--;
        mutex_exit(&iport->refcnt_lock);
        if (iport->refcnt == 0) {
                cv_signal(&iport->refcnt_cv);
        }
        pmcs_prt(iport->pwp, PMCS_PRT_DEBUG2, NULL, NULL, "%s: iport "
            "[0x%p] refcnt (%d)", __func__, (void *)iport, iport->refcnt);
}

void
pmcs_phymap_activate(void *arg, char *ua, void **privp)
{
        _NOTE(ARGUNUSED(privp));
        pmcs_hw_t       *pwp = arg;
        pmcs_iport_t    *iport = NULL;

        mutex_enter(&pwp->lock);
        if ((pwp->state == STATE_UNPROBING) || (pwp->state == STATE_DEAD) ||
            (pwp->state == STATE_IN_RESET)) {
                mutex_exit(&pwp->lock);
                return;
        }
        pwp->phymap_active++;
        mutex_exit(&pwp->lock);

        if (scsi_hba_iportmap_iport_add(pwp->hss_iportmap, ua, NULL) !=
            DDI_SUCCESS) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL, "%s: failed to "
                    "add iport handle on unit address [%s]", __func__, ua);
        } else {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL, "%s: "
                    "phymap_active count (%d), added iport handle on unit "
                    "address [%s]", __func__, pwp->phymap_active, ua);
        }

        /* Set the HBA softstate as our private data for this unit address */
        *privp = (void *)pwp;

        /*
         * We are waiting on attach for this iport node, unless it is still
         * attached. This can happen if a consumer has an outstanding open
         * on our iport node, but the port is down.  If this is the case, we
         * need to configure our iport here for reuse.
         */
        iport = pmcs_get_iport_by_ua(pwp, ua);
        if (iport) {
                mutex_enter(&iport->lock);
                if (pmcs_iport_configure_phys(iport) != DDI_SUCCESS) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL, "%s: "
                            "failed to configure phys on iport [0x%p] at "
                            "unit address (%s)", __func__, (void *)iport, ua);
                }
                pmcs_iport_active(iport);
                pmcs_smhba_add_iport_prop(iport, DATA_TYPE_INT32, PMCS_NUM_PHYS,
                    &iport->nphy);
                mutex_exit(&iport->lock);
                pmcs_rele_iport(iport);
        }

}

void
pmcs_phymap_deactivate(void *arg, char *ua, void *privp)
{
        _NOTE(ARGUNUSED(privp));
        pmcs_hw_t       *pwp = arg;
        pmcs_iport_t    *iport;

        mutex_enter(&pwp->lock);
        pwp->phymap_active--;
        mutex_exit(&pwp->lock);

        if (scsi_hba_iportmap_iport_remove(pwp->hss_iportmap, ua) !=
            DDI_SUCCESS) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL, "%s: failed to "
                    "remove iport handle on unit address [%s]", __func__, ua);
        } else {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL, "%s: "
                    "phymap_active count (%d), removed iport handle on unit "
                    "address [%s]", __func__, pwp->phymap_active, ua);
        }

        iport = pmcs_get_iport_by_ua(pwp, ua);

        if (iport == NULL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL, "%s: failed "
                    "lookup of iport handle on unit addr (%s)", __func__, ua);
                return;
        }

        mutex_enter(&iport->lock);
        iport->ua_state = UA_INACTIVE;
        iport->portid = PMCS_IPORT_INVALID_PORT_ID;
        pmcs_remove_phy_from_iport(iport, NULL);
        mutex_exit(&iport->lock);
        pmcs_rele_iport(iport);
}

/*
 * Top-level discovery function
 */
void
pmcs_discover(pmcs_hw_t *pwp)
{
        pmcs_phy_t              *pptr;
        pmcs_phy_t              *root_phy;

        DTRACE_PROBE2(pmcs__discover__entry, ulong_t, pwp->work_flags,
            boolean_t, pwp->config_changed);

        mutex_enter(&pwp->lock);

        if (pwp->state != STATE_RUNNING) {
                mutex_exit(&pwp->lock);
                return;
        }

        /* Ensure we have at least one phymap active */
        if (pwp->phymap_active == 0) {
                mutex_exit(&pwp->lock);
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL,
                    "%s: phymap inactive, exiting", __func__);
                return;
        }

        mutex_exit(&pwp->lock);

        /*
         * If no iports have attached, but we have PHYs that are up, we
         * are waiting for iport attach to complete.  Restart discovery.
         */
        rw_enter(&pwp->iports_lock, RW_READER);
        if (!pwp->iports_attached) {
                rw_exit(&pwp->iports_lock);
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL,
                    "%s: no iports attached, retry discovery", __func__);
                SCHEDULE_WORK(pwp, PMCS_WORK_DISCOVER);
                return;
        }
        rw_exit(&pwp->iports_lock);

        mutex_enter(&pwp->config_lock);
        if (pwp->configuring) {
                mutex_exit(&pwp->config_lock);
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL,
                    "%s: configuration already in progress", __func__);
                return;
        }

        if (pmcs_acquire_scratch(pwp, B_FALSE)) {
                mutex_exit(&pwp->config_lock);
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL,
                    "%s: cannot allocate scratch", __func__);
                SCHEDULE_WORK(pwp, PMCS_WORK_DISCOVER);
                return;
        }

        pwp->configuring = 1;
        pwp->config_changed = B_FALSE;
        mutex_exit(&pwp->config_lock);

        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL, "Discovery begin");

        /*
         * First, tell SCSA that we're beginning set operations.
         */
        pmcs_begin_observations(pwp);

        /*
         * The order of the following traversals is important.
         *
         * The first one checks for changed expanders.
         *
         * The second one aborts commands for dead devices and deregisters them.
         *
         * The third one clears the contents of dead expanders from the tree
         *
         * The fourth one clears now dead devices in expanders that remain.
         */

        /*
         * 1. Check expanders marked changed (but not dead) to see if they still
         * have the same number of phys and the same SAS address. Mark them,
         * their subsidiary phys (if wide) and their descendents dead if
         * anything has changed. Check the devices they contain to see if
         * *they* have changed. If they've changed from type NOTHING we leave
         * them marked changed to be configured later (picking up a new SAS
         * address and link rate if possible). Otherwise, any change in type,
         * SAS address or removal of target role will cause us to mark them
         * (and their descendents) as dead (and cause any pending commands
         * and associated devices to be removed).
         *
         * NOTE: We don't want to bail on discovery if the config has
         * changed until *after* we run pmcs_kill_devices.
         */
        root_phy = pwp->root_phys;
        pmcs_check_expanders(pwp, root_phy);

        /*
         * 2. Descend the tree looking for dead devices and kill them
         * by aborting all active commands and then deregistering them.
         */
        if (pmcs_kill_devices(pwp, root_phy)) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL,
                    "%s: pmcs_kill_devices failed!", __func__);
        }

        /*
         * 3. Check for dead expanders and remove their children from the tree.
         * By the time we get here, the devices and commands for them have
         * already been terminated and removed.
         *
         * We do this independent of the configuration count changing so we can
         * free any dead device PHYs that were discovered while checking
         * expanders. We ignore any subsidiary phys as pmcs_clear_expander
         * will take care of those.
         *
         * NOTE: pmcs_clear_expander requires softstate lock
         */
        mutex_enter(&pwp->lock);
        for (pptr = pwp->root_phys; pptr; pptr = pptr->sibling) {
                /*
                 * Call pmcs_clear_expander for every root PHY.  It will
                 * recurse and determine which (if any) expanders actually
                 * need to be cleared.
                 */
                pmcs_lock_phy(pptr);
                pmcs_clear_expander(pwp, pptr, 0);
                pmcs_unlock_phy(pptr);
        }
        mutex_exit(&pwp->lock);

        /*
         * 4. Check for dead devices and nullify them. By the time we get here,
         * the devices and commands for them have already been terminated
         * and removed. This is different from step 2 in that this just nulls
         * phys that are part of expanders that are still here but used to
         * be something but are no longer something (e.g., after a pulled
         * disk drive). Note that dead expanders had their contained phys
         * removed from the tree- here, the expanders themselves are
         * nullified (unless they were removed by being contained in another
         * expander phy).
         */
        pmcs_clear_phys(pwp, root_phy);

        /*
         * 5. Now check for and configure new devices.
         */
        if (pmcs_configure_new_devices(pwp, root_phy)) {
                goto restart;
        }

        DTRACE_PROBE2(pmcs__discover__exit, ulong_t, pwp->work_flags,
            boolean_t, pwp->config_changed);
        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL, "Discovery end");

        mutex_enter(&pwp->config_lock);

        if (pwp->config_changed == B_FALSE) {
                /*
                 * Observation is stable, report what we currently see to
                 * the tgtmaps for delta processing. Start by setting
                 * BEGIN on all tgtmaps.
                 */
                mutex_exit(&pwp->config_lock);
                if (pmcs_report_observations(pwp) == B_FALSE) {
                        goto restart;
                }
                mutex_enter(&pwp->config_lock);
        } else {
                /*
                 * If config_changed is TRUE, we need to reschedule
                 * discovery now.
                 */
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL,
                    "%s: Config has changed, will re-run discovery", __func__);
                SCHEDULE_WORK(pwp, PMCS_WORK_DISCOVER);
        }

        pmcs_release_scratch(pwp);
        if (!pwp->quiesced) {
                pwp->blocked = 0;
        }
        pwp->configuring = 0;
        cv_signal(&pwp->config_cv);
        mutex_exit(&pwp->config_lock);

#ifdef DEBUG
        pptr = pmcs_find_phy_needing_work(pwp, pwp->root_phys);
        if (pptr != NULL) {
                if (!WORK_IS_SCHEDULED(pwp, PMCS_WORK_DISCOVER)) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                            "PHY %s dead=%d changed=%d configured=%d "
                            "but no work scheduled", pptr->path, pptr->dead,
                            pptr->changed, pptr->configured);
                }
                pmcs_unlock_phy(pptr);
        }
#endif

        return;

restart:
        /* Clean up and restart discovery */
        pmcs_release_scratch(pwp);
        pmcs_flush_observations(pwp);
        mutex_enter(&pwp->config_lock);
        pwp->configuring = 0;
        cv_signal(&pwp->config_cv);
        RESTART_DISCOVERY_LOCKED(pwp);
        mutex_exit(&pwp->config_lock);
}

#ifdef DEBUG
/*
 * Return any PHY that needs to have scheduled work done.  The PHY is returned
 * locked.
 */
static pmcs_phy_t *
pmcs_find_phy_needing_work(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        pmcs_phy_t *cphyp, *pnext;

        while (pptr) {
                pmcs_lock_phy(pptr);

                if (pptr->changed || (pptr->dead && pptr->valid_device_id)) {
                        return (pptr);
                }

                pnext = pptr->sibling;

                if (pptr->children) {
                        cphyp = pptr->children;
                        pmcs_unlock_phy(pptr);
                        cphyp = pmcs_find_phy_needing_work(pwp, cphyp);
                        if (cphyp) {
                                return (cphyp);
                        }
                } else {
                        pmcs_unlock_phy(pptr);
                }

                pptr = pnext;
        }

        return (NULL);
}
#endif /* DEBUG */

/*
 * We may (or may not) report observations to SCSA.  This is prefaced by
 * issuing a set_begin for each iport target map.
 */
static void
pmcs_begin_observations(pmcs_hw_t *pwp)
{
        pmcs_iport_t            *iport;
        scsi_hba_tgtmap_t       *tgtmap;

        rw_enter(&pwp->iports_lock, RW_READER);
        for (iport = list_head(&pwp->iports); iport != NULL;
            iport = list_next(&pwp->iports, iport)) {
                /*
                 * Unless we have at least one phy up, skip this iport.
                 * Note we don't need to lock the iport for report_skip
                 * since it is only used here.  We are doing the skip so that
                 * the phymap and iportmap stabilization times are honored -
                 * giving us the ability to recover port operation within the
                 * stabilization time without unconfiguring targets using the
                 * port.
                 */
                if (!sas_phymap_uahasphys(pwp->hss_phymap, iport->ua)) {
                        iport->report_skip = 1;
                        continue;               /* skip set_begin */
                }
                iport->report_skip = 0;

                tgtmap = iport->iss_tgtmap;
                ASSERT(tgtmap);
                if (scsi_hba_tgtmap_set_begin(tgtmap) != DDI_SUCCESS) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL,
                            "%s: cannot set_begin tgtmap ", __func__);
                        rw_exit(&pwp->iports_lock);
                        return;
                }
                pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL,
                    "%s: set begin on tgtmap [0x%p]", __func__, (void *)tgtmap);
        }
        rw_exit(&pwp->iports_lock);
}

/*
 * Tell SCSA to flush the observations we've already sent (if any), as they
 * are no longer valid.
 */
static void
pmcs_flush_observations(pmcs_hw_t *pwp)
{
        pmcs_iport_t            *iport;
        scsi_hba_tgtmap_t       *tgtmap;

        rw_enter(&pwp->iports_lock, RW_READER);
        for (iport = list_head(&pwp->iports); iport != NULL;
            iport = list_next(&pwp->iports, iport)) {
                /*
                 * Skip this iport if it has no PHYs up.
                 */
                if (!sas_phymap_uahasphys(pwp->hss_phymap, iport->ua)) {
                        continue;
                }

                tgtmap = iport->iss_tgtmap;
                ASSERT(tgtmap);
                if (scsi_hba_tgtmap_set_flush(tgtmap) != DDI_SUCCESS) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL,
                            "%s: Failed set_flush on tgtmap 0x%p", __func__,
                            (void *)tgtmap);
                } else {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL,
                            "%s: set flush on tgtmap 0x%p", __func__,
                            (void *)tgtmap);
                }
        }
        rw_exit(&pwp->iports_lock);
}

/*
 * Report current observations to SCSA.
 */
static boolean_t
pmcs_report_observations(pmcs_hw_t *pwp)
{
        pmcs_iport_t            *iport;
        scsi_hba_tgtmap_t       *tgtmap;
        char                    *ap;
        pmcs_phy_t              *pptr;
        uint64_t                wwn;

        /*
         * Observation is stable, report what we currently see to the tgtmaps
         * for delta processing.
         */
        pptr = pwp->root_phys;

        while (pptr) {
                pmcs_lock_phy(pptr);

                /*
                 * Skip PHYs that have nothing attached or are dead.
                 */
                if ((pptr->dtype == NOTHING) || pptr->dead) {
                        pmcs_unlock_phy(pptr);
                        pptr = pptr->sibling;
                        continue;
                }

                if (pptr->changed) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: oops, PHY %s changed; restart discovery",
                            __func__, pptr->path);
                        pmcs_unlock_phy(pptr);
                        return (B_FALSE);
                }

                /*
                 * Get the iport for this root PHY, then call the helper
                 * to report observations for this iport's targets
                 */
                wwn = pmcs_barray2wwn(pptr->sas_address);
                pmcs_unlock_phy(pptr);
                iport = pmcs_get_iport_by_wwn(pwp, wwn);
                if (iport == NULL) {
                        /* No iport for this tgt */
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL,
                            "%s: no iport for this target", __func__);
                        pptr = pptr->sibling;
                        continue;
                }

                pmcs_lock_phy(pptr);
                if (!iport->report_skip) {
                        if (pmcs_report_iport_observations(
                            pwp, iport, pptr) == B_FALSE) {
                                pmcs_rele_iport(iport);
                                pmcs_unlock_phy(pptr);
                                return (B_FALSE);
                        }
                }
                pmcs_rele_iport(iport);
                pmcs_unlock_phy(pptr);
                pptr = pptr->sibling;
        }

        /*
         * The observation is complete, end sets. Note we will skip any
         * iports that are active, but have no PHYs in them (i.e. awaiting
         * unconfigure). Set to restart discovery if we find this.
         */
        rw_enter(&pwp->iports_lock, RW_READER);
        for (iport = list_head(&pwp->iports);
            iport != NULL;
            iport = list_next(&pwp->iports, iport)) {

                if (iport->report_skip)
                        continue;               /* skip set_end */

                tgtmap = iport->iss_tgtmap;
                ASSERT(tgtmap);
                if (scsi_hba_tgtmap_set_end(tgtmap, 0) != DDI_SUCCESS) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL,
                            "%s: cannot set_end tgtmap ", __func__);
                        rw_exit(&pwp->iports_lock);
                        return (B_FALSE);
                }
                pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, NULL, NULL,
                    "%s: set end on tgtmap [0x%p]", __func__, (void *)tgtmap);
        }

        /*
         * Now that discovery is complete, set up the necessary
         * DDI properties on each iport node.
         */
        for (iport = list_head(&pwp->iports); iport != NULL;
            iport = list_next(&pwp->iports, iport)) {
                /* Set up the 'attached-port' property on the iport */
                ap = kmem_zalloc(PMCS_MAX_UA_SIZE, KM_SLEEP);
                mutex_enter(&iport->lock);
                pptr = iport->pptr;
                mutex_exit(&iport->lock);
                if (pptr == NULL) {
                        /*
                         * This iport is down, but has not been
                         * removed from our list (unconfigured).
                         * Set our value to '0'.
                         */
                        (void) snprintf(ap, 1, "%s", "0");
                } else {
                        /* Otherwise, set it to remote phy's wwn */
                        pmcs_lock_phy(pptr);
                        wwn = pmcs_barray2wwn(pptr->sas_address);
                        (void) scsi_wwn_to_wwnstr(wwn, 1, ap);
                        pmcs_unlock_phy(pptr);
                }
                if (ndi_prop_update_string(DDI_DEV_T_NONE, iport->dip,
                    SCSI_ADDR_PROP_ATTACHED_PORT, ap) != DDI_SUCCESS) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL, "%s: Failed "
                            "to set prop ("SCSI_ADDR_PROP_ATTACHED_PORT")",
                            __func__);
                }
                kmem_free(ap, PMCS_MAX_UA_SIZE);
        }
        rw_exit(&pwp->iports_lock);

        return (B_TRUE);
}

/*
 * Report observations into a particular iport's target map
 *
 * Called with phyp (and all descendents) locked
 */
static boolean_t
pmcs_report_iport_observations(pmcs_hw_t *pwp, pmcs_iport_t *iport,
    pmcs_phy_t *phyp)
{
        pmcs_phy_t              *lphyp;
        scsi_hba_tgtmap_t       *tgtmap;
        scsi_tgtmap_tgt_type_t  tgt_type;
        char                    *ua;
        uint64_t                wwn;

        tgtmap = iport->iss_tgtmap;
        ASSERT(tgtmap);

        lphyp = phyp;
        while (lphyp) {
                switch (lphyp->dtype) {
                default:                /* Skip unknown PHYs. */
                        /* for non-root phys, skip to sibling */
                        goto next_phy;

                case SATA:
                case SAS:
                        tgt_type = SCSI_TGT_SCSI_DEVICE;
                        break;

                case EXPANDER:
                        tgt_type = SCSI_TGT_SMP_DEVICE;
                        break;
                }

                if (lphyp->dead || !lphyp->configured) {
                        goto next_phy;
                }

                /*
                 * Validate the PHY's SAS address
                 */
                if (((lphyp->sas_address[0] & 0xf0) >> 4) != NAA_IEEE_REG) {
                        pmcs_prt(pwp, PMCS_PRT_ERR, lphyp, NULL,
                            "PHY 0x%p (%s) has invalid SAS address; "
                            "will not enumerate", (void *)lphyp, lphyp->path);
                        goto next_phy;
                }

                wwn = pmcs_barray2wwn(lphyp->sas_address);
                ua = scsi_wwn_to_wwnstr(wwn, 1, NULL);

                pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP, lphyp, NULL,
                    "iport_observation: adding %s on tgtmap [0x%p] phy [0x%p]",
                    ua, (void *)tgtmap, (void*)lphyp);

                if (scsi_hba_tgtmap_set_add(tgtmap, tgt_type, ua, NULL) !=
                    DDI_SUCCESS) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_MAP,  NULL, NULL,
                            "%s: failed to add address %s", __func__, ua);
                        scsi_free_wwnstr(ua);
                        return (B_FALSE);
                }
                scsi_free_wwnstr(ua);

                if (lphyp->children) {
                        if (pmcs_report_iport_observations(pwp, iport,
                            lphyp->children) == B_FALSE) {
                                return (B_FALSE);
                        }
                }

                /* for non-root phys, report siblings too */
next_phy:
                if (IS_ROOT_PHY(lphyp)) {
                        lphyp = NULL;
                } else {
                        lphyp = lphyp->sibling;
                }
        }

        return (B_TRUE);
}

/*
 * Check for and configure new devices.
 *
 * If the changed device is a SATA device, add a SATA device.
 *
 * If the changed device is a SAS device, add a SAS device.
 *
 * If the changed device is an EXPANDER device, do a REPORT
 * GENERAL SMP command to find out the number of contained phys.
 *
 * For each number of contained phys, allocate a phy, do a
 * DISCOVERY SMP command to find out what kind of device it
 * is and add it to the linked list of phys on the *next* level.
 *
 * NOTE: pptr passed in by the caller will be a root PHY
 */
static int
pmcs_configure_new_devices(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        int rval = 0;
        pmcs_iport_t *iport;
        pmcs_phy_t *pnext, *orig_pptr = pptr, *root_phy, *pchild;
        uint64_t wwn;

        /*
         * First, walk through each PHY at this level
         */
        while (pptr) {
                pmcs_lock_phy(pptr);
                pnext = pptr->sibling;

                /*
                 * Set the new dtype if it has changed
                 */
                if ((pptr->pend_dtype != NEW) &&
                    (pptr->pend_dtype != pptr->dtype)) {
                        pptr->dtype = pptr->pend_dtype;
                }

                if (pptr->changed == 0 || pptr->dead || pptr->configured) {
                        goto next_phy;
                }

                /* Confirm that this iport is configured */
                root_phy = pmcs_get_root_phy(pptr);
                wwn = pmcs_barray2wwn(root_phy->sas_address);
                pmcs_unlock_phy(pptr);
                iport = pmcs_get_iport_by_wwn(pwp, wwn);
                if (iport == NULL) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, NULL,
                            "%s: iport not yet configured, "
                            "retry discovery", __func__);
                        pnext = NULL;
                        rval = -1;
                        pmcs_lock_phy(pptr);
                        goto next_phy;
                }

                pmcs_lock_phy(pptr);
                switch (pptr->dtype) {
                case NOTHING:
                        pptr->changed = 0;
                        break;
                case SATA:
                case SAS:
                        pptr->iport = iport;
                        pmcs_new_tport(pwp, pptr);
                        break;
                case EXPANDER:
                        pmcs_configure_expander(pwp, pptr, iport);
                        break;
                }
                pmcs_rele_iport(iport);

                mutex_enter(&pwp->config_lock);
                if (pwp->config_changed) {
                        mutex_exit(&pwp->config_lock);
                        pnext = NULL;
                        goto next_phy;
                }
                mutex_exit(&pwp->config_lock);

next_phy:
                pmcs_unlock_phy(pptr);
                pptr = pnext;
        }

        if (rval != 0) {
                return (rval);
        }

        /*
         * Now walk through each PHY again, recalling ourselves if they
         * have children
         */
        pptr = orig_pptr;
        while (pptr) {
                pmcs_lock_phy(pptr);
                pnext = pptr->sibling;
                pchild = pptr->children;
                pmcs_unlock_phy(pptr);

                if (pchild) {
                        rval = pmcs_configure_new_devices(pwp, pchild);
                        if (rval != 0) {
                                break;
                        }
                }

                pptr = pnext;
        }

        return (rval);
}

/*
 * Set all phys and descendent phys as changed if changed == B_TRUE, otherwise
 * mark them all as not changed.
 *
 * Called with parent PHY locked.
 */
void
pmcs_set_changed(pmcs_hw_t *pwp, pmcs_phy_t *parent, boolean_t changed,
    int level)
{
        pmcs_phy_t *pptr;

        if (level == 0) {
                if (changed) {
                        PHY_CHANGED(pwp, parent);
                } else {
                        parent->changed = 0;
                }
                if (parent->dtype == EXPANDER && parent->level) {
                        parent->width = 1;
                }
                if (parent->children) {
                        pmcs_set_changed(pwp, parent->children, changed,
                            level + 1);
                }
        } else {
                pptr = parent;
                while (pptr) {
                        if (changed) {
                                PHY_CHANGED(pwp, pptr);
                        } else {
                                pptr->changed = 0;
                        }
                        if (pptr->dtype == EXPANDER && pptr->level) {
                                pptr->width = 1;
                        }
                        if (pptr->children) {
                                pmcs_set_changed(pwp, pptr->children, changed,
                                    level + 1);
                        }
                        pptr = pptr->sibling;
                }
        }
}

/*
 * Take the passed phy mark it and its descendants as dead.
 * Fire up reconfiguration to abort commands and bury it.
 *
 * Called with the parent PHY locked.
 */
void
pmcs_kill_changed(pmcs_hw_t *pwp, pmcs_phy_t *parent, int level)
{
        pmcs_phy_t *pptr = parent;

        while (pptr) {
                pptr->link_rate = 0;
                pptr->abort_sent = 0;
                pptr->abort_pending = 1;
                SCHEDULE_WORK(pwp, PMCS_WORK_ABORT_HANDLE);
                pptr->need_rl_ext = 0;

                if (pptr->dead == 0) {
                        PHY_CHANGED(pwp, pptr);
                        RESTART_DISCOVERY(pwp);
                }

                pptr->dead = 1;

                if (pptr->children) {
                        pmcs_kill_changed(pwp, pptr->children, level + 1);
                }

                /*
                 * Only kill siblings at level > 0
                 */
                if (level == 0) {
                        return;
                }

                pptr = pptr->sibling;
        }
}

/*
 * Go through every PHY and clear any that are dead (unless they're expanders)
 */
static void
pmcs_clear_phys(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        pmcs_phy_t *pnext, *phyp;

        phyp = pptr;
        while (phyp) {
                if (IS_ROOT_PHY(phyp)) {
                        pmcs_lock_phy(phyp);
                }

                if ((phyp->dtype != EXPANDER) && phyp->dead) {
                        pmcs_clear_phy(pwp, phyp);
                }

                if (phyp->children) {
                        pmcs_clear_phys(pwp, phyp->children);
                }

                pnext = phyp->sibling;

                if (IS_ROOT_PHY(phyp)) {
                        pmcs_unlock_phy(phyp);
                }

                phyp = pnext;
        }
}

/*
 * Clear volatile parts of a phy.  Called with PHY locked.
 */
void
pmcs_clear_phy(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL, "%s: %s",
            __func__, pptr->path);
        ASSERT(mutex_owned(&pptr->phy_lock));
        /* keep sibling */
        /* keep children */
        /* keep parent */
        pptr->device_id = PMCS_INVALID_DEVICE_ID;
        /* keep hw_event_ack */
        pptr->ncphy = 0;
        /* keep phynum */
        pptr->width = 0;
        pptr->ds_recovery_retries = 0;
        pptr->ds_prev_good_recoveries = 0;
        pptr->last_good_recovery = 0;
        pptr->prev_recovery = 0;

        /* keep dtype */
        pptr->config_stop = 0;
        pptr->spinup_hold = 0;
        pptr->atdt = 0;
        /* keep portid */
        pptr->link_rate = 0;
        pptr->valid_device_id = 0;
        pptr->abort_sent = 0;
        pptr->abort_pending = 0;
        pptr->need_rl_ext = 0;
        pptr->subsidiary = 0;
        pptr->configured = 0;
        pptr->deregister_wait = 0;
        pptr->reenumerate = 0;
        /* Only mark dead if it's not a root PHY and its dtype isn't NOTHING */
        /* XXX: What about directly attached disks? */
        if (!IS_ROOT_PHY(pptr) && (pptr->dtype != NOTHING))
                pptr->dead = 1;
        pptr->changed = 0;
        /* keep SAS address */
        /* keep path */
        /* keep ref_count */
        /* Don't clear iport on root PHYs - they are handled in pmcs_intr.c */
        if (!IS_ROOT_PHY(pptr)) {
                pptr->last_iport = pptr->iport;
                pptr->iport = NULL;
        }
        /* keep target */
}

/*
 * Allocate softstate for this target if there isn't already one.  If there
 * is, just redo our internal configuration.  If it is actually "new", we'll
 * soon get a tran_tgt_init for it.
 *
 * Called with PHY locked.
 */
static void
pmcs_new_tport(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL, "%s: phy 0x%p @ %s",
            __func__, (void *)pptr, pptr->path);

        if (pmcs_configure_phy(pwp, pptr) == B_FALSE) {
                /*
                 * If the config failed, mark the PHY as changed.
                 */
                PHY_CHANGED(pwp, pptr);
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                    "%s: pmcs_configure_phy failed for phy 0x%p", __func__,
                    (void *)pptr);
                return;
        }

        /* Mark PHY as no longer changed */
        pptr->changed = 0;

        /*
         * If the PHY has no target pointer:
         *
         * If it's a root PHY, see if another PHY in the iport holds the
         * target pointer (primary PHY changed).  If so, move it over.
         *
         * If it's not a root PHY, see if there's a PHY on the dead_phys
         * list that matches.
         */
        if (pptr->target == NULL) {
                if (IS_ROOT_PHY(pptr)) {
                        pmcs_phy_t *rphy = pwp->root_phys;

                        while (rphy) {
                                if (rphy == pptr) {
                                        rphy = rphy->sibling;
                                        continue;
                                }

                                mutex_enter(&rphy->phy_lock);
                                if ((rphy->iport == pptr->iport) &&
                                    (rphy->target != NULL)) {
                                        mutex_enter(&rphy->target->statlock);
                                        pptr->target = rphy->target;
                                        rphy->target = NULL;
                                        pptr->target->phy = pptr;
                                        /* The target is now on pptr */
                                        mutex_exit(&pptr->target->statlock);
                                        mutex_exit(&rphy->phy_lock);
                                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG,
                                            pptr, pptr->target,
                                            "%s: Moved target from %s to %s",
                                            __func__, rphy->path, pptr->path);
                                        break;
                                }
                                mutex_exit(&rphy->phy_lock);

                                rphy = rphy->sibling;
                        }
                } else {
                        pmcs_reap_dead_phy(pptr);
                }
        }

        /*
         * Only assign the device if there is a target for this PHY with a
         * matching SAS address.  If an iport is disconnected from one piece
         * of storage and connected to another within the iport stabilization
         * time, we can get the PHY/target mismatch situation.
         *
         * Otherwise, it'll get done in tran_tgt_init.
         */
        if (pptr->target) {
                mutex_enter(&pptr->target->statlock);
                if (pmcs_phy_target_match(pptr) == B_FALSE) {
                        mutex_exit(&pptr->target->statlock);
                        if (!IS_ROOT_PHY(pptr)) {
                                pmcs_dec_phy_ref_count(pptr);
                        }
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                            "%s: Not assigning existing tgt %p for PHY %p "
                            "(WWN mismatch)", __func__, (void *)pptr->target,
                            (void *)pptr);
                        pptr->target = NULL;
                        return;
                }

                if (!pmcs_assign_device(pwp, pptr->target)) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, pptr->target,
                            "%s: pmcs_assign_device failed for target 0x%p",
                            __func__, (void *)pptr->target);
                }
                mutex_exit(&pptr->target->statlock);
        }
}

/*
 * Called with PHY lock held.
 */
static boolean_t
pmcs_configure_phy(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        char *dtype;

        ASSERT(mutex_owned(&pptr->phy_lock));

        /*
         * Mark this device as no longer changed.
         */
        pptr->changed = 0;

        /*
         * If we don't have a device handle, get one.
         */
        if (pmcs_get_device_handle(pwp, pptr)) {
                return (B_FALSE);
        }

        pptr->configured = 1;

        switch (pptr->dtype) {
        case SAS:
                dtype = "SAS";
                break;
        case SATA:
                dtype = "SATA";
                break;
        case EXPANDER:
                dtype = "SMP";
                break;
        default:
                dtype = "???";
        }

        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL, "config_dev: %s "
            "dev %s " SAS_ADDR_FMT " dev id 0x%x lr 0x%x", dtype, pptr->path,
            SAS_ADDR_PRT(pptr->sas_address), pptr->device_id, pptr->link_rate);

        return (B_TRUE);
}

/*
 * Called with PHY locked
 */
static void
pmcs_configure_expander(pmcs_hw_t *pwp, pmcs_phy_t *pptr, pmcs_iport_t *iport)
{
        pmcs_phy_t *ctmp, *clist = NULL, *cnext;
        int result, i, nphy = 0;
        boolean_t root_phy = B_FALSE;

        ASSERT(iport);

        /*
         * Step 1- clear our "changed" bit. If we need to retry/restart due
         * to resource shortages, we'll set it again. While we're doing
         * configuration, other events may set it again as well.  If the PHY
         * is a root PHY and is currently marked as having changed, reset the
         * config_stop timer as well.
         */
        if (IS_ROOT_PHY(pptr) && pptr->changed) {
                pptr->config_stop = ddi_get_lbolt() +
                    drv_usectohz(PMCS_MAX_CONFIG_TIME);
        }
        pptr->changed = 0;

        /*
         * Step 2- make sure we don't overflow
         */
        if (pptr->level == PMCS_MAX_XPND-1) {
                pmcs_prt(pwp, PMCS_PRT_WARN, pptr, NULL,
                    "%s: SAS expansion tree too deep", __func__);
                return;
        }

        /*
         * Step 3- Check if this expander is part of a wide phy that has
         * already been configured.
         *
         * This is known by checking this level for another EXPANDER device
         * with the same SAS address and isn't already marked as a subsidiary
         * phy and a parent whose SAS address is the same as our SAS address
         * (if there are parents).
         */
        if (!IS_ROOT_PHY(pptr)) {
                /*
                 * No need to lock the parent here because we're in discovery
                 * and the only time a PHY's children pointer can change is
                 * in discovery; either in pmcs_clear_expander (which has
                 * already been called) or here, down below.  Plus, trying to
                 * grab the parent's lock here can cause deadlock.
                 */
                ctmp = pptr->parent->children;
        } else {
                ctmp = pwp->root_phys;
                root_phy = B_TRUE;
        }

        while (ctmp) {
                /*
                 * If we've checked all PHYs up to pptr, we stop. Otherwise,
                 * we'll be checking for a primary PHY with a higher PHY
                 * number than pptr, which will never happen.  The primary
                 * PHY on non-root expanders will ALWAYS be the lowest
                 * numbered PHY.
                 */
                if (ctmp == pptr) {
                        break;
                }

                /*
                 * If pptr and ctmp are root PHYs, just grab the mutex on
                 * ctmp.  No need to lock the entire tree.  If they are not
                 * root PHYs, there is no need to lock since a non-root PHY's
                 * SAS address and other characteristics can only change in
                 * discovery anyway.
                 */
                if (root_phy) {
                        mutex_enter(&ctmp->phy_lock);
                }

                if (ctmp->dtype == EXPANDER && ctmp->width &&
                    memcmp(ctmp->sas_address, pptr->sas_address, 8) == 0) {
                        int widephy = 0;
                        /*
                         * If these phys are not root PHYs, compare their SAS
                         * addresses too.
                         */
                        if (!root_phy) {
                                if (memcmp(ctmp->parent->sas_address,
                                    pptr->parent->sas_address, 8) == 0) {
                                        widephy = 1;
                                }
                        } else {
                                widephy = 1;
                        }
                        if (widephy) {
                                ctmp->width++;
                                pptr->subsidiary = 1;

                                /*
                                 * Update the primary PHY's attached-port-pm
                                 * and target-port-pm information with the info
                                 * from this subsidiary
                                 */
                                pmcs_update_phy_pm_props(ctmp,
                                    pptr->att_port_pm_tmp,
                                    pptr->tgt_port_pm_tmp, B_TRUE);

                                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                                    "%s: PHY %s part of wide PHY %s "
                                    "(now %d wide)", __func__, pptr->path,
                                    ctmp->path, ctmp->width);
                                if (root_phy) {
                                        mutex_exit(&ctmp->phy_lock);
                                }
                                return;
                        }
                }

                cnext = ctmp->sibling;
                if (root_phy) {
                        mutex_exit(&ctmp->phy_lock);
                }
                ctmp = cnext;
        }

        /*
         * Step 4- If we don't have a device handle, get one.  Since this
         * is the primary PHY, make sure subsidiary is cleared.
         */
        pptr->subsidiary = 0;
        pptr->iport = iport;
        if (pmcs_get_device_handle(pwp, pptr)) {
                goto out;
        }
        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL, "Config expander %s "
            SAS_ADDR_FMT " dev id 0x%x lr 0x%x", pptr->path,
            SAS_ADDR_PRT(pptr->sas_address), pptr->device_id, pptr->link_rate);

        /*
         * Step 5- figure out how many phys are in this expander.
         */
        nphy = pmcs_expander_get_nphy(pwp, pptr);
        if (nphy <= 0) {
                if (nphy == 0 && ddi_get_lbolt() < pptr->config_stop) {
                        PHY_CHANGED(pwp, pptr);
                        RESTART_DISCOVERY(pwp);
                } else {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: Retries exhausted for %s, killing", __func__,
                            pptr->path);
                        pptr->config_stop = 0;
                        pmcs_kill_changed(pwp, pptr, 0);
                }
                goto out;
        }

        /*
         * Step 6- Allocate a list of phys for this expander and figure out
         * what each one is.
         */
        for (i = 0; i < nphy; i++) {
                ctmp = kmem_cache_alloc(pwp->phy_cache, KM_SLEEP);
                bzero(ctmp, sizeof (pmcs_phy_t));
                ctmp->device_id = PMCS_INVALID_DEVICE_ID;
                ctmp->sibling = clist;
                ctmp->pend_dtype = NEW; /* Init pending dtype */
                ctmp->config_stop = ddi_get_lbolt() +
                    drv_usectohz(PMCS_MAX_CONFIG_TIME);
                clist = ctmp;
        }

        mutex_enter(&pwp->config_lock);
        if (pwp->config_changed) {
                RESTART_DISCOVERY_LOCKED(pwp);
                mutex_exit(&pwp->config_lock);
                /*
                 * Clean up the newly allocated PHYs and return
                 */
                while (clist) {
                        ctmp = clist->sibling;
                        clist->target_addr = NULL;
                        kmem_cache_free(pwp->phy_cache, clist);
                        clist = ctmp;
                }
                return;
        }
        mutex_exit(&pwp->config_lock);

        /*
         * Step 7- Now fill in the rest of the static portions of the phy.
         */
        for (i = 0, ctmp = clist; ctmp; ctmp = ctmp->sibling, i++) {
                ctmp->parent = pptr;
                ctmp->pwp = pwp;
                ctmp->level = pptr->level+1;
                ctmp->portid = pptr->portid;
                if (ctmp->tolerates_sas2) {
                        ASSERT(i < SAS2_PHYNUM_MAX);
                        ctmp->phynum = i & SAS2_PHYNUM_MASK;
                } else {
                        ASSERT(i < SAS_PHYNUM_MAX);
                        ctmp->phynum = i & SAS_PHYNUM_MASK;
                }
                pmcs_phy_name(pwp, ctmp, ctmp->path, sizeof (ctmp->path));
                pmcs_lock_phy(ctmp);
        }

        /*
         * Step 8- Discover things about each phy in the expander.
         */
        for (i = 0, ctmp = clist; ctmp; ctmp = ctmp->sibling, i++) {
                result = pmcs_expander_content_discover(pwp, pptr, ctmp);
                if (result <= 0) {
                        if (ddi_get_lbolt() < pptr->config_stop) {
                                PHY_CHANGED(pwp, pptr);
                                RESTART_DISCOVERY(pwp);
                        } else {
                                pptr->config_stop = 0;
                                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                                    "%s: Retries exhausted for %s, killing",
                                    __func__, pptr->path);
                                pmcs_kill_changed(pwp, pptr, 0);
                        }
                        goto out;
                }

                /* Set pend_dtype to dtype for 1st time initialization */
                ctmp->pend_dtype = ctmp->dtype;
        }

        /*
         * Step 9: Install the new list on the next level. There should
         * typically be no children pointer on this PHY.  There is one known
         * case where this can happen, though.  If a root PHY goes down and
         * comes back up before discovery can run, we will fail to remove the
         * children from that PHY since it will no longer be marked dead.
         * However, in this case, all children should also be marked dead.  If
         * we see that, take those children and put them on the dead_phys list.
         */
        if (pptr->children != NULL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: Expander @ %s still has children: Clean up",
                    __func__, pptr->path);
                pmcs_add_dead_phys(pwp, pptr->children);
        }

        /*
         * Set the new children pointer for this expander
         */
        pptr->children = clist;
        clist = NULL;
        pptr->ncphy = nphy;
        pptr->configured = 1;

        /*
         * We only set width if we're greater than level 0.
         */
        if (pptr->level) {
                pptr->width = 1;
        }

        /*
         * Now tell the rest of the world about us, as an SMP node.
         */
        pptr->iport = iport;
        pmcs_new_tport(pwp, pptr);

out:
        while (clist) {
                ctmp = clist->sibling;
                pmcs_unlock_phy(clist);
                clist->target_addr = NULL;
                kmem_cache_free(pwp->phy_cache, clist);
                clist = ctmp;
        }
}

/*
 * 2. Check expanders marked changed (but not dead) to see if they still have
 * the same number of phys and the same SAS address. Mark them, their subsidiary
 * phys (if wide) and their descendents dead if anything has changed. Check the
 * the devices they contain to see if *they* have changed. If they've changed
 * from type NOTHING we leave them marked changed to be configured later
 * (picking up a new SAS address and link rate if possible). Otherwise, any
 * change in type, SAS address or removal of target role will cause us to
 * mark them (and their descendents) as dead and cause any pending commands
 * and associated devices to be removed.
 *
 * Called with PHY (pptr) locked.
 */

static void
pmcs_check_expander(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        int nphy, result;
        pmcs_phy_t *ctmp, *local, *local_list = NULL, *local_tail = NULL;
        boolean_t kill_changed, changed;

        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
            "%s: check %s", __func__, pptr->path);

        /*
         * Step 1: Mark phy as not changed. We will mark it changed if we need
         * to retry.
         */
        pptr->changed = 0;

        /*
         * Reset the config_stop time. Although we're not actually configuring
         * anything here, we do want some indication of when to give up trying
         * if we can't communicate with the expander.
         */
        pptr->config_stop = ddi_get_lbolt() +
            drv_usectohz(PMCS_MAX_CONFIG_TIME);

        /*
         * Step 2: Figure out how many phys are in this expander. If
         * pmcs_expander_get_nphy returns 0 we ran out of resources,
         * so reschedule and try later. If it returns another error,
         * just return.
         */
        nphy = pmcs_expander_get_nphy(pwp, pptr);
        if (nphy <= 0) {
                if ((nphy == 0) && (ddi_get_lbolt() < pptr->config_stop)) {
                        PHY_CHANGED(pwp, pptr);
                        RESTART_DISCOVERY(pwp);
                } else {
                        pptr->config_stop = 0;
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: Retries exhausted for %s, killing", __func__,
                            pptr->path);
                        pmcs_kill_changed(pwp, pptr, 0);
                }
                return;
        }

        /*
         * Step 3: If the number of phys don't agree, kill the old sub-tree.
         */
        if (nphy != pptr->ncphy) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                    "%s: number of contained phys for %s changed from %d to %d",
                    __func__, pptr->path, pptr->ncphy, nphy);
                /*
                 * Force a rescan of this expander after dead contents
                 * are cleared and removed.
                 */
                pmcs_kill_changed(pwp, pptr, 0);
                return;
        }

        /*
         * Step 4: if we're at the bottom of the stack, we're done
         * (we can't have any levels below us)
         */
        if (pptr->level == PMCS_MAX_XPND-1) {
                return;
        }

        /*
         * Step 5: Discover things about each phy in this expander.  We do
         * this by walking the current list of contained phys and doing a
         * content discovery for it to a local phy.
         */
        ctmp = pptr->children;
        ASSERT(ctmp);
        if (ctmp == NULL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                    "%s: No children attached to expander @ %s?", __func__,
                    pptr->path);
                return;
        }

        while (ctmp) {
                /*
                 * Allocate a local PHY to contain the proposed new contents
                 * and link it to the rest of the local PHYs so that they
                 * can all be freed later.
                 */
                local = pmcs_clone_phy(ctmp);

                if (local_list == NULL) {
                        local_list = local;
                        local_tail = local;
                } else {
                        local_tail->sibling = local;
                        local_tail = local;
                }

                /*
                 * Need to lock the local PHY since pmcs_expander_content_
                 * discovery may call pmcs_clear_phy on it, which expects
                 * the PHY to be locked.
                 */
                pmcs_lock_phy(local);
                result = pmcs_expander_content_discover(pwp, pptr, local);
                pmcs_unlock_phy(local);
                if (result <= 0) {
                        if (ddi_get_lbolt() < pptr->config_stop) {
                                PHY_CHANGED(pwp, pptr);
                                RESTART_DISCOVERY(pwp);
                        } else {
                                pptr->config_stop = 0;
                                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                                    "%s: Retries exhausted for %s, killing",
                                    __func__, pptr->path);
                                pmcs_kill_changed(pwp, pptr, 0);
                        }

                        /*
                         * Release all the local PHYs that we allocated.
                         */
                        pmcs_free_phys(pwp, local_list);
                        return;
                }

                ctmp = ctmp->sibling;
        }

        /*
         * Step 6: Compare the local PHY's contents to our current PHY.  If
         * there are changes, take the appropriate action.
         * This is done in two steps (step 5 above, and 6 here) so that if we
         * have to bail during this process (e.g. pmcs_expander_content_discover
         * fails), we haven't actually changed the state of any of the real
         * PHYs.  Next time we come through here, we'll be starting over from
         * scratch.  This keeps us from marking a changed PHY as no longer
         * changed, but then having to bail only to come back next time and
         * think that the PHY hadn't changed.  If this were to happen, we
         * would fail to properly configure the device behind this PHY.
         */
        local = local_list;
        ctmp = pptr->children;

        while (ctmp) {
                changed = B_FALSE;
                kill_changed = B_FALSE;

                /*
                 * We set local to local_list prior to this loop so that we
                 * can simply walk the local_list while we walk this list.  The
                 * two lists should be completely in sync.
                 *
                 * Clear the changed flag here.
                 */
                ctmp->changed = 0;

                if (ctmp->dtype != local->dtype) {
                        if (ctmp->dtype != NOTHING) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, ctmp, NULL,
                                    "%s: %s type changed from %s to %s "
                                    "(killing)", __func__, ctmp->path,
                                    PHY_TYPE(ctmp), PHY_TYPE(local));
                                /*
                                 * Force a rescan of this expander after dead
                                 * contents are cleared and removed.
                                 */
                                changed = B_TRUE;
                                kill_changed = B_TRUE;
                        } else {
                                changed = B_TRUE;
                                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, ctmp, NULL,
                                    "%s: %s type changed from NOTHING to %s",
                                    __func__, ctmp->path, PHY_TYPE(local));
                                /*
                                 * Since this PHY was nothing and is now
                                 * something, reset the config_stop timer.
                                 */
                                ctmp->config_stop = ddi_get_lbolt() +
                                    drv_usectohz(PMCS_MAX_CONFIG_TIME);
                        }

                } else if (ctmp->atdt != local->atdt) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, ctmp, NULL, "%s: "
                            "%s attached device type changed from %d to %d "
                            "(killing)", __func__, ctmp->path, ctmp->atdt,
                            local->atdt);
                        /*
                         * Force a rescan of this expander after dead
                         * contents are cleared and removed.
                         */
                        changed = B_TRUE;

                        if (local->atdt == 0) {
                                kill_changed = B_TRUE;
                        }
                } else if (ctmp->link_rate != local->link_rate) {
                        pmcs_prt(pwp, PMCS_PRT_INFO, ctmp, NULL, "%s: %s "
                            "changed speed from %s to %s", __func__, ctmp->path,
                            pmcs_get_rate(ctmp->link_rate),
                            pmcs_get_rate(local->link_rate));
                        /* If the speed changed from invalid, force rescan */
                        if (!PMCS_VALID_LINK_RATE(ctmp->link_rate)) {
                                changed = B_TRUE;
                                RESTART_DISCOVERY(pwp);
                        } else {
                                /* Just update to the new link rate */
                                ctmp->link_rate = local->link_rate;
                        }

                        if (!PMCS_VALID_LINK_RATE(local->link_rate)) {
                                kill_changed = B_TRUE;
                        }
                } else if (memcmp(ctmp->sas_address, local->sas_address,
                    sizeof (ctmp->sas_address)) != 0) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, ctmp, NULL,
                            "%s: SAS Addr for %s changed from " SAS_ADDR_FMT
                            "to " SAS_ADDR_FMT " (kill old tree)", __func__,
                            ctmp->path, SAS_ADDR_PRT(ctmp->sas_address),
                            SAS_ADDR_PRT(local->sas_address));
                        /*
                         * Force a rescan of this expander after dead
                         * contents are cleared and removed.
                         */
                        changed = B_TRUE;
                } else {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, ctmp, NULL,
                            "%s: %s looks the same (type %s)",
                            __func__, ctmp->path, PHY_TYPE(ctmp));
                        /*
                         * If EXPANDER, still mark it changed so we
                         * re-evaluate its contents.  If it's not an expander,
                         * but it hasn't been configured, also mark it as
                         * changed so that it will undergo configuration.
                         */
                        if (ctmp->dtype == EXPANDER) {
                                changed = B_TRUE;
                        } else if ((ctmp->dtype != NOTHING) &&
                            !ctmp->configured) {
                                ctmp->changed = 1;
                        } else {
                                /* It simply hasn't changed */
                                ctmp->changed = 0;
                        }
                }

                /*
                 * If the PHY changed, call pmcs_kill_changed if indicated,
                 * update its contents to reflect its current state and mark it
                 * as changed.
                 */
                if (changed) {
                        /*
                         * pmcs_kill_changed will mark the PHY as changed, so
                         * only do PHY_CHANGED if we did not do kill_changed.
                         */
                        if (kill_changed) {
                                pmcs_kill_changed(pwp, ctmp, 0);
                        } else {
                                /*
                                 * If we're not killing the device, it's not
                                 * dead.  Mark the PHY as changed.
                                 */
                                PHY_CHANGED(pwp, ctmp);

                                if (ctmp->dead) {
                                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG,
                                            ctmp, NULL, "%s: Unmarking PHY %s "
                                            "dead, restarting discovery",
                                            __func__, ctmp->path);
                                        ctmp->dead = 0;
                                        RESTART_DISCOVERY(pwp);
                                }
                        }

                        /*
                         * If the dtype of this PHY is now NOTHING, mark it as
                         * unconfigured.  Set pend_dtype to what the new dtype
                         * is.  It'll get updated at the end of the discovery
                         * process.
                         */
                        if (local->dtype == NOTHING) {
                                bzero(ctmp->sas_address,
                                    sizeof (local->sas_address));
                                ctmp->atdt = 0;
                                ctmp->link_rate = 0;
                                ctmp->pend_dtype = NOTHING;
                                ctmp->configured = 0;
                        } else {
                                (void) memcpy(ctmp->sas_address,
                                    local->sas_address,
                                    sizeof (local->sas_address));
                                ctmp->atdt = local->atdt;
                                ctmp->link_rate = local->link_rate;
                                ctmp->pend_dtype = local->dtype;
                                ctmp->att_port_pm_tmp = local->att_port_pm_tmp;
                                ctmp->tgt_port_pm_tmp = local->tgt_port_pm_tmp;
                        }
                }

                local = local->sibling;
                ctmp = ctmp->sibling;
        }

        /*
         * If we got to here, that means we were able to see all the PHYs
         * and we can now update all of the real PHYs with the information
         * we got on the local PHYs.  Once that's done, free all the local
         * PHYs.
         */

        pmcs_free_phys(pwp, local_list);
}

/*
 * Top level routine to check expanders.  We call pmcs_check_expander for
 * each expander.  Since we're not doing any configuration right now, it
 * doesn't matter if this is breadth-first.
 */
static void
pmcs_check_expanders(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        pmcs_phy_t *phyp, *pnext, *pchild;

        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
            "%s: %s", __func__, pptr->path);

        /*
         * Check each expander at this level
         */
        phyp = pptr;
        while (phyp) {
                pmcs_lock_phy(phyp);

                if ((phyp->dtype == EXPANDER) && phyp->changed &&
                    !phyp->dead && !phyp->subsidiary &&
                    phyp->configured) {
                        pmcs_check_expander(pwp, phyp);
                }

                pnext = phyp->sibling;
                pmcs_unlock_phy(phyp);
                phyp = pnext;
        }

        /*
         * Now check the children
         */
        phyp = pptr;
        while (phyp) {
                pmcs_lock_phy(phyp);
                pnext = phyp->sibling;
                pchild = phyp->children;
                pmcs_unlock_phy(phyp);

                if (pchild) {
                        pmcs_check_expanders(pwp, pchild);
                }

                phyp = pnext;
        }
}

/*
 * Called with softstate and PHY locked
 */
static void
pmcs_clear_expander(pmcs_hw_t *pwp, pmcs_phy_t *pptr, int level)
{
        pmcs_phy_t *ctmp;

        ASSERT(mutex_owned(&pwp->lock));
        ASSERT(mutex_owned(&pptr->phy_lock));
        ASSERT(pptr->level < PMCS_MAX_XPND - 1);

        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
            "%s: checking %s", __func__, pptr->path);

        ctmp = pptr->children;
        while (ctmp) {
                /*
                 * If the expander is dead, mark its children dead
                 */
                if (pptr->dead) {
                        ctmp->dead = 1;
                }
                if (ctmp->dtype == EXPANDER) {
                        pmcs_clear_expander(pwp, ctmp, level + 1);
                }
                ctmp = ctmp->sibling;
        }

        /*
         * If this expander is not dead, we're done here.
         */
        if (!pptr->dead) {
                return;
        }

        /*
         * Now snip out the list of children below us and release them
         */
        if (pptr->children) {
                pmcs_add_dead_phys(pwp, pptr->children);
        }

        pptr->children = NULL;

        /*
         * Clear subsidiary phys as well.  Getting the parent's PHY lock
         * is only necessary if level == 0 since otherwise the parent is
         * already locked.
         */
        if (!IS_ROOT_PHY(pptr)) {
                if (level == 0) {
                        mutex_enter(&pptr->parent->phy_lock);
                }
                ctmp = pptr->parent->children;
                if (level == 0) {
                        mutex_exit(&pptr->parent->phy_lock);
                }
        } else {
                ctmp = pwp->root_phys;
        }

        while (ctmp) {
                if (ctmp == pptr) {
                        ctmp = ctmp->sibling;
                        continue;
                }
                /*
                 * We only need to lock subsidiary PHYs on the level 0
                 * expander.  Any children of that expander, subsidiaries or
                 * not, will already be locked.
                 */
                if (level == 0) {
                        pmcs_lock_phy(ctmp);
                }
                if (ctmp->dtype != EXPANDER || ctmp->subsidiary == 0 ||
                    memcmp(ctmp->sas_address, pptr->sas_address,
                    sizeof (ctmp->sas_address)) != 0) {
                        if (level == 0) {
                                pmcs_unlock_phy(ctmp);
                        }
                        ctmp = ctmp->sibling;
                        continue;
                }
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, ctmp, NULL,
                    "%s: subsidiary %s", __func__, ctmp->path);
                pmcs_clear_phy(pwp, ctmp);
                if (level == 0) {
                        pmcs_unlock_phy(ctmp);
                }
                ctmp = ctmp->sibling;
        }

        pmcs_clear_phy(pwp, pptr);
}

/*
 * Called with PHY locked and with scratch acquired. We return 0 if
 * we fail to allocate resources or notice that the configuration
 * count changed while we were running the command. We return
 * less than zero if we had an I/O error or received an unsupported
 * configuration. Otherwise we return the number of phys in the
 * expander.
 */
#define DFM(m, y) if (m == NULL) m = y
static int
pmcs_expander_get_nphy(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        struct pmcwork *pwrk;
        pmcs_iport_t *iport;
        char buf[64];
        const uint_t rdoff = 0x100;     /* returned data offset */
        smp_response_frame_t *srf;
        smp_report_general_resp_t *srgr;
        uint32_t msg[PMCS_MSG_SIZE], *ptr, htag, status, ival;
        int result = 0;

        ival = 0x40001100;

again:
        if (!pptr->iport || !pptr->valid_device_id) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, pptr->target,
                    "%s: Can't reach PHY %s", __func__, pptr->path);
                goto out;
        }

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr);
        if (pwrk == NULL) {
                goto out;
        }
        (void) memset(pwp->scratch, 0x77, PMCS_SCRATCH_SIZE);
        pwrk->arg = pwp->scratch;
        pwrk->dtype = pptr->dtype;
        pwrk->xp = pptr->target;
        pwrk->htag |= PMCS_TAG_NONIO_CMD;
        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (ptr == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_prt(pwp, PMCS_PRT_DEBUG2, pptr, NULL,
                    "%s: GET_IQ_ENTRY failed", __func__);
                pmcs_pwork(pwp, pwrk);
                goto out;
        }

        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL, PMCIN_SMP_REQUEST));
        msg[1] = LE_32(pwrk->htag);
        msg[2] = LE_32(pptr->device_id);
        msg[3] = LE_32((4 << SMP_REQUEST_LENGTH_SHIFT) | SMP_INDIRECT_RESPONSE);
        /*
         * Send SMP REPORT GENERAL (of either SAS1.1 or SAS2 flavors).
         */
        msg[4] = BE_32(ival);
        msg[5] = 0;
        msg[6] = 0;
        msg[7] = 0;
        msg[8] = 0;
        msg[9] = 0;
        msg[10] = 0;
        msg[11] = 0;
        msg[12] = LE_32(DWORD0(pwp->scratch_dma+rdoff));
        msg[13] = LE_32(DWORD1(pwp->scratch_dma+rdoff));
        msg[14] = LE_32(PMCS_SCRATCH_SIZE - rdoff);
        msg[15] = 0;

        COPY_MESSAGE(ptr, msg, PMCS_MSG_SIZE);

        pmcs_hold_iport(pptr->iport);
        iport = pptr->iport;
        pmcs_smp_acquire(iport);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        htag = pwrk->htag;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        pmcs_unlock_phy(pptr);
        WAIT_FOR(pwrk, 1000, result);
        pmcs_pwork(pwp, pwrk);
        pmcs_smp_release(iport);
        pmcs_rele_iport(iport);
        pmcs_lock_phy(pptr);
        if (result) {
                pmcs_timed_out(pwp, htag, __func__);
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                    "%s: Issuing SMP ABORT for htag 0x%08x", __func__, htag);
                if (pmcs_abort(pwp, pptr, htag, 0, 1)) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: SMP ABORT failed for cmd (htag 0x%08x)",
                            __func__, htag);
                }
                result = 0;
                goto out;
        }

        mutex_enter(&pwp->config_lock);
        if (pwp->config_changed) {
                RESTART_DISCOVERY_LOCKED(pwp);
                mutex_exit(&pwp->config_lock);
                result = 0;
                goto out;
        }
        mutex_exit(&pwp->config_lock);

        ptr = (void *)pwp->scratch;
        status = LE_32(ptr[2]);
        if (status == PMCOUT_STATUS_UNDERFLOW ||
            status == PMCOUT_STATUS_OVERFLOW) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_UNDERFLOW, pptr, NULL,
                    "%s: over/underflow", __func__);
                status = PMCOUT_STATUS_OK;
        }
        srf = (smp_response_frame_t *)&((uint32_t *)pwp->scratch)[rdoff >> 2];
        srgr = (smp_report_general_resp_t *)
            &((uint32_t *)pwp->scratch)[(rdoff >> 2)+1];

        if (status != PMCOUT_STATUS_OK) {
                char *nag = NULL;
                (void) snprintf(buf, sizeof (buf),
                    "%s: SMP op failed (0x%x)", __func__, status);
                switch (status) {
                case PMCOUT_STATUS_IO_PORT_IN_RESET:
                        DFM(nag, "I/O Port In Reset");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_ERROR_HW_TIMEOUT:
                        DFM(nag, "Hardware Timeout");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_ERROR_INTERNAL_SMP_RESOURCE:
                        DFM(nag, "Internal SMP Resource Failure");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_XFER_ERR_PHY_NOT_READY:
                        DFM(nag, "PHY Not Ready");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_OPEN_CNX_ERROR_CONNECTION_RATE_NOT_SUPPORTED:
                        DFM(nag, "Connection Rate Not Supported");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_IO_XFER_OPEN_RETRY_TIMEOUT:
                        DFM(nag, "Open Retry Timeout");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_IO_OPEN_CNX_ERROR_HW_RESOURCE_BUSY:
                        DFM(nag, "HW Resource Busy");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_SMP_RESP_CONNECTION_ERROR:
                        DFM(nag, "Response Connection Error");
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                            "%s: expander %s SMP operation failed (%s)",
                            __func__, pptr->path, nag);
                        break;

                /*
                 * For the IO_DS_NON_OPERATIONAL case, we need to kick off
                 * device state recovery and return 0 so that the caller
                 * doesn't assume this expander is dead for good.
                 */
                case PMCOUT_STATUS_IO_DS_NON_OPERATIONAL: {
                        pmcs_xscsi_t *xp = pptr->target;

                        pmcs_prt(pwp, PMCS_PRT_DEBUG_DEV_STATE, pptr, xp,
                            "%s: expander %s device state non-operational",
                            __func__, pptr->path);

                        if (xp == NULL) {
                                /*
                                 * Kick off recovery right now.
                                 */
                                SCHEDULE_WORK(pwp, PMCS_WORK_DS_ERR_RECOVERY);
                                (void) ddi_taskq_dispatch(pwp->tq, pmcs_worker,
                                    pwp, DDI_NOSLEEP);
                        } else {
                                mutex_enter(&xp->statlock);
                                pmcs_start_dev_state_recovery(xp, pptr);
                                mutex_exit(&xp->statlock);
                        }

                        break;
                }

                default:
                        pmcs_print_entry(pwp, PMCS_PRT_DEBUG, buf, ptr);
                        result = -EIO;
                        break;
                }
        } else if (srf->srf_frame_type != SMP_FRAME_TYPE_RESPONSE) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: bad response frame type 0x%x",
                    __func__, srf->srf_frame_type);
                result = -EINVAL;
        } else if (srf->srf_function != SMP_FUNC_REPORT_GENERAL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: bad response function 0x%x",
                    __func__, srf->srf_function);
                result = -EINVAL;
        } else if (srf->srf_result != 0) {
                /*
                 * Check to see if we have a value of 3 for failure and
                 * whether we were using a SAS2.0 allocation length value
                 * and retry without it.
                 */
                if (srf->srf_result == 3 && (ival & 0xff00)) {
                        ival &= ~0xff00;
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                            "%s: err 0x%x with SAS2 request- retry with SAS1",
                            __func__, srf->srf_result);
                        goto again;
                }
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: bad response 0x%x", __func__, srf->srf_result);
                result = -EINVAL;
        } else if (srgr->srgr_configuring) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: expander at phy %s is still configuring",
                    __func__, pptr->path);
                result = 0;
        } else {
                result = srgr->srgr_number_of_phys;
                if (ival & 0xff00) {
                        pptr->tolerates_sas2 = 1;
                }
                /*
                 * Save off the REPORT_GENERAL response
                 */
                bcopy(srgr, &pptr->rg_resp, sizeof (smp_report_general_resp_t));
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                    "%s has %d phys and %s SAS2", pptr->path, result,
                    pptr->tolerates_sas2? "tolerates" : "does not tolerate");
        }
out:
        return (result);
}

/*
 * Called with expander locked (and thus, pptr) as well as all PHYs up to
 * the root, and scratch acquired. Return 0 if we fail to allocate resources
 * or notice that the configuration changed while we were running the command.
 *
 * We return less than zero if we had an I/O error or received an
 * unsupported configuration.
 */
static int
pmcs_expander_content_discover(pmcs_hw_t *pwp, pmcs_phy_t *expander,
    pmcs_phy_t *pptr)
{
        struct pmcwork *pwrk;
        pmcs_iport_t *iport;
        char buf[64];
        uint8_t sas_address[8];
        uint8_t att_sas_address[8];
        smp_response_frame_t *srf;
        smp_discover_resp_t *sdr;
        const uint_t rdoff = 0x100;     /* returned data offset */
        uint8_t *roff;
        uint32_t status, *ptr, msg[PMCS_MSG_SIZE], htag;
        int result = 0;
        uint8_t ini_support;
        uint8_t tgt_support;

        if (!expander->iport || !expander->valid_device_id) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, expander, expander->target,
                    "%s: Can't reach PHY %s", __func__, expander->path);
                goto out;
        }

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, expander);
        if (pwrk == NULL) {
                goto out;
        }
        (void) memset(pwp->scratch, 0x77, PMCS_SCRATCH_SIZE);
        pwrk->arg = pwp->scratch;
        pwrk->dtype = expander->dtype;
        pwrk->xp = expander->target;
        pwrk->htag |= PMCS_TAG_NONIO_CMD;
        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL, PMCIN_SMP_REQUEST));
        msg[1] = LE_32(pwrk->htag);
        msg[2] = LE_32(expander->device_id);
        msg[3] = LE_32((12 << SMP_REQUEST_LENGTH_SHIFT) |
            SMP_INDIRECT_RESPONSE);
        /*
         * Send SMP DISCOVER (of either SAS1.1 or SAS2 flavors).
         */
        if (expander->tolerates_sas2) {
                msg[4] = BE_32(0x40101B00);
        } else {
                msg[4] = BE_32(0x40100000);
        }
        msg[5] = 0;
        msg[6] = BE_32((pptr->phynum << 16));
        msg[7] = 0;
        msg[8] = 0;
        msg[9] = 0;
        msg[10] = 0;
        msg[11] = 0;
        msg[12] = LE_32(DWORD0(pwp->scratch_dma+rdoff));
        msg[13] = LE_32(DWORD1(pwp->scratch_dma+rdoff));
        msg[14] = LE_32(PMCS_SCRATCH_SIZE - rdoff);
        msg[15] = 0;
        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (ptr == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                goto out;
        }

        COPY_MESSAGE(ptr, msg, PMCS_MSG_SIZE);

        pmcs_hold_iport(expander->iport);
        iport = expander->iport;
        pmcs_smp_acquire(iport);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        htag = pwrk->htag;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        pmcs_unlock_phy(expander);
        WAIT_FOR(pwrk, 1000, result);
        pmcs_pwork(pwp, pwrk);
        pmcs_smp_release(iport);
        pmcs_rele_iport(iport);
        pmcs_lock_phy(expander);
        if (result) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                    "%s: Issuing SMP ABORT for htag 0x%08x", __func__, htag);
                if (pmcs_abort(pwp, pptr, htag, 0, 1)) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: SMP ABORT failed for cmd (htag 0x%08x)",
                            __func__, htag);
                }
                result = -ETIMEDOUT;
                goto out;
        }

        mutex_enter(&pwp->config_lock);
        if (pwp->config_changed) {
                RESTART_DISCOVERY_LOCKED(pwp);
                mutex_exit(&pwp->config_lock);
                result = 0;
                goto out;
        }

        mutex_exit(&pwp->config_lock);
        ptr = (void *)pwp->scratch;
        /*
         * Point roff to the DMA offset for returned data
         */
        roff = pwp->scratch;
        roff += rdoff;
        srf = (smp_response_frame_t *)roff;
        sdr = (smp_discover_resp_t *)(roff+4);
        status = LE_32(ptr[2]);
        if (status == PMCOUT_STATUS_UNDERFLOW ||
            status == PMCOUT_STATUS_OVERFLOW) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_UNDERFLOW, pptr, NULL,
                    "%s: over/underflow", __func__);
                status = PMCOUT_STATUS_OK;
        }
        if (status != PMCOUT_STATUS_OK) {
                char *nag = NULL;
                (void) snprintf(buf, sizeof (buf),
                    "%s: SMP op failed (0x%x)", __func__, status);
                switch (status) {
                case PMCOUT_STATUS_ERROR_HW_TIMEOUT:
                        DFM(nag, "Hardware Timeout");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_ERROR_INTERNAL_SMP_RESOURCE:
                        DFM(nag, "Internal SMP Resource Failure");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_XFER_ERR_PHY_NOT_READY:
                        DFM(nag, "PHY Not Ready");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_OPEN_CNX_ERROR_CONNECTION_RATE_NOT_SUPPORTED:
                        DFM(nag, "Connection Rate Not Supported");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_IO_XFER_OPEN_RETRY_TIMEOUT:
                        DFM(nag, "Open Retry Timeout");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_IO_OPEN_CNX_ERROR_HW_RESOURCE_BUSY:
                        DFM(nag, "HW Resource Busy");
                        /* FALLTHROUGH */
                case PMCOUT_STATUS_SMP_RESP_CONNECTION_ERROR:
                        DFM(nag, "Response Connection Error");
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                            "%s: expander %s SMP operation failed (%s)",
                            __func__, pptr->path, nag);
                        break;
                default:
                        pmcs_print_entry(pwp, PMCS_PRT_DEBUG, buf, ptr);
                        result = -EIO;
                        break;
                }
                goto out;
        } else if (srf->srf_frame_type != SMP_FRAME_TYPE_RESPONSE) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: bad response frame type 0x%x",
                    __func__, srf->srf_frame_type);
                result = -EINVAL;
                goto out;
        } else if (srf->srf_function != SMP_FUNC_DISCOVER) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: bad response function 0x%x",
                    __func__, srf->srf_function);
                result = -EINVAL;
                goto out;
        } else if (srf->srf_result != SMP_RES_FUNCTION_ACCEPTED) {
                result = pmcs_smp_function_result(pwp, srf);
                /* Need not fail if PHY is Vacant */
                if (result != SMP_RES_PHY_VACANT) {
                        result = -EINVAL;
                        goto out;
                }
        }

        /*
         * Save off the DISCOVER response
         */
        bcopy(sdr, &pptr->disc_resp, sizeof (smp_discover_resp_t));

        ini_support = (sdr->sdr_attached_sata_host |
            (sdr->sdr_attached_smp_initiator << 1) |
            (sdr->sdr_attached_stp_initiator << 2) |
            (sdr->sdr_attached_ssp_initiator << 3));

        tgt_support = (sdr->sdr_attached_sata_device |
            (sdr->sdr_attached_smp_target << 1) |
            (sdr->sdr_attached_stp_target << 2) |
            (sdr->sdr_attached_ssp_target << 3));

        pmcs_wwn2barray(BE_64(sdr->sdr_sas_addr), sas_address);
        pmcs_wwn2barray(BE_64(sdr->sdr_attached_sas_addr), att_sas_address);

        pptr->virtual = sdr->sdr_virtual_phy;

        /*
         * Set the routing attribute regardless of the PHY type.
         */
        pptr->routing_attr = sdr->sdr_routing_attr;

        switch (sdr->sdr_attached_device_type) {
        case SAS_IF_DTYPE_ENDPOINT:
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                    "exp_content: %s atdt=0x%x lr=%x is=%x ts=%x SAS="
                    SAS_ADDR_FMT " attSAS=" SAS_ADDR_FMT " atPHY=%x",
                    pptr->path,
                    sdr->sdr_attached_device_type,
                    sdr->sdr_negotiated_logical_link_rate,
                    ini_support,
                    tgt_support,
                    SAS_ADDR_PRT(sas_address),
                    SAS_ADDR_PRT(att_sas_address),
                    sdr->sdr_attached_phy_identifier);

                if (sdr->sdr_attached_sata_device ||
                    sdr->sdr_attached_stp_target) {
                        pptr->dtype = SATA;
                } else if (sdr->sdr_attached_ssp_target) {
                        pptr->dtype = SAS;
                } else if (tgt_support || ini_support) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s: %s has tgt support=%x init support=(%x)",
                            __func__, pptr->path, tgt_support, ini_support);
                }

                switch (pptr->routing_attr) {
                case SMP_ROUTING_SUBTRACTIVE:
                case SMP_ROUTING_TABLE:
                case SMP_ROUTING_DIRECT:
                        pptr->routing_method = SMP_ROUTING_DIRECT;
                        break;
                default:
                        pptr->routing_method = 0xff;    /* Invalid method */
                        break;
                }
                pmcs_update_phy_pm_props(pptr, (1ULL << pptr->phynum),
                    (1ULL << sdr->sdr_attached_phy_identifier), B_TRUE);
                break;
        case SAS_IF_DTYPE_EDGE:
        case SAS_IF_DTYPE_FANOUT:
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                    "exp_content: %s atdt=0x%x lr=%x is=%x ts=%x SAS="
                    SAS_ADDR_FMT " attSAS=" SAS_ADDR_FMT " atPHY=%x",
                    pptr->path,
                    sdr->sdr_attached_device_type,
                    sdr->sdr_negotiated_logical_link_rate,
                    ini_support,
                    tgt_support,
                    SAS_ADDR_PRT(sas_address),
                    SAS_ADDR_PRT(att_sas_address),
                    sdr->sdr_attached_phy_identifier);
                if (sdr->sdr_attached_smp_target) {
                        /*
                         * Avoid configuring phys that just point back
                         * at a parent phy
                         */
                        if (expander->parent &&
                            memcmp(expander->parent->sas_address,
                            att_sas_address,
                            sizeof (expander->parent->sas_address)) == 0) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG3, pptr, NULL,
                                    "%s: skipping port back to parent "
                                    "expander (%s)", __func__, pptr->path);
                                pptr->dtype = NOTHING;
                                break;
                        }
                        pptr->dtype = EXPANDER;

                } else if (tgt_support || ini_support) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                            "%s has tgt support=%x init support=(%x)",
                            pptr->path, tgt_support, ini_support);
                        pptr->dtype = EXPANDER;
                }
                if (pptr->routing_attr == SMP_ROUTING_DIRECT) {
                        pptr->routing_method = 0xff;    /* Invalid method */
                } else {
                        pptr->routing_method = pptr->routing_attr;
                }
                pmcs_update_phy_pm_props(pptr, (1ULL << pptr->phynum),
                    (1ULL << sdr->sdr_attached_phy_identifier), B_TRUE);
                break;
        default:
                pptr->dtype = NOTHING;
                break;
        }
        if (pptr->dtype != NOTHING) {
                pmcs_phy_t *ctmp;

                /*
                 * If the attached device is a SATA device and the expander
                 * is (possibly) a SAS2 compliant expander, check for whether
                 * there is a NAA=5 WWN field starting at this offset and
                 * use that for the SAS Address for this device.
                 */
                if (expander->tolerates_sas2 && pptr->dtype == SATA &&
                    (roff[SAS_ATTACHED_NAME_OFFSET] >> 8) == NAA_IEEE_REG) {
                        (void) memcpy(pptr->sas_address,
                            &roff[SAS_ATTACHED_NAME_OFFSET], 8);
                } else {
                        (void) memcpy(pptr->sas_address, att_sas_address, 8);
                }
                pptr->atdt = (sdr->sdr_attached_device_type);
                /*
                 * Now run up from the expander's parent up to the top to
                 * make sure we only use the least common link_rate.
                 */
                for (ctmp = expander->parent; ctmp; ctmp = ctmp->parent) {
                        if (ctmp->link_rate <
                            sdr->sdr_negotiated_logical_link_rate) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, NULL,
                                    "%s: derating link rate from %x to %x due "
                                    "to %s being slower", pptr->path,
                                    sdr->sdr_negotiated_logical_link_rate,
                                    ctmp->link_rate,
                                    ctmp->path);
                                sdr->sdr_negotiated_logical_link_rate =
                                    ctmp->link_rate;
                        }
                }
                pptr->link_rate = sdr->sdr_negotiated_logical_link_rate;
                pptr->state.prog_min_rate = sdr->sdr_prog_min_phys_link_rate;
                pptr->state.hw_min_rate = sdr->sdr_hw_min_phys_link_rate;
                pptr->state.prog_max_rate = sdr->sdr_prog_max_phys_link_rate;
                pptr->state.hw_max_rate = sdr->sdr_hw_max_phys_link_rate;
                PHY_CHANGED(pwp, pptr);
        } else {
                pmcs_clear_phy(pwp, pptr);
        }
        result = 1;
out:
        return (result);
}

/*
 * Get a work structure and assign it a tag with type and serial number
 * If a structure is returned, it is returned locked.
 */
pmcwork_t *
pmcs_gwork(pmcs_hw_t *pwp, uint32_t tag_type, pmcs_phy_t *phyp)
{
        pmcwork_t *p;
        uint16_t snum;
        uint32_t off;

        mutex_enter(&pwp->wfree_lock);
        p = STAILQ_FIRST(&pwp->wf);
        if (p == NULL) {
                /*
                 * If we couldn't get a work structure, it's time to bite
                 * the bullet, grab the pfree_lock and copy over all the
                 * work structures from the pending free list to the actual
                 * free list (assuming it's not also empty).
                 */
                mutex_enter(&pwp->pfree_lock);
                if (STAILQ_FIRST(&pwp->pf) == NULL) {
                        mutex_exit(&pwp->pfree_lock);
                        mutex_exit(&pwp->wfree_lock);
                        return (NULL);
                }
                pwp->wf.stqh_first = pwp->pf.stqh_first;
                pwp->wf.stqh_last = pwp->pf.stqh_last;
                STAILQ_INIT(&pwp->pf);
                mutex_exit(&pwp->pfree_lock);

                p = STAILQ_FIRST(&pwp->wf);
                ASSERT(p != NULL);
        }
        STAILQ_REMOVE(&pwp->wf, p, pmcwork, next);
        snum = pwp->wserno++;
        mutex_exit(&pwp->wfree_lock);

        off = p - pwp->work;

        mutex_enter(&p->lock);
        ASSERT(p->state == PMCS_WORK_STATE_NIL);
        ASSERT(p->htag == PMCS_TAG_FREE);
        p->htag = (tag_type << PMCS_TAG_TYPE_SHIFT) & PMCS_TAG_TYPE_MASK;
        p->htag |= ((snum << PMCS_TAG_SERNO_SHIFT) & PMCS_TAG_SERNO_MASK);
        p->htag |= ((off << PMCS_TAG_INDEX_SHIFT) & PMCS_TAG_INDEX_MASK);
        p->start = gethrtime();
        p->state = PMCS_WORK_STATE_READY;
        p->ssp_event = 0;
        p->dead = 0;
        p->timer = 0;

        if (phyp) {
                p->phy = phyp;
                pmcs_inc_phy_ref_count(phyp);
        }

        return (p);
}

/*
 * Called with pwrk lock held.  Returned with lock released.
 */
void
pmcs_pwork(pmcs_hw_t *pwp, pmcwork_t *p)
{
        ASSERT(p != NULL);
        ASSERT(mutex_owned(&p->lock));

        p->last_ptr = p->ptr;
        p->last_arg = p->arg;
        p->last_phy = p->phy;
        p->last_xp = p->xp;
        p->last_htag = p->htag;
        p->last_state = p->state;
        p->finish = gethrtime();

        if (p->phy) {
                pmcs_dec_phy_ref_count(p->phy);
        }

        p->state = PMCS_WORK_STATE_NIL;
        p->htag = PMCS_TAG_FREE;
        p->xp = NULL;
        p->ptr = NULL;
        p->arg = NULL;
        p->phy = NULL;
        p->abt_htag = 0;
        p->timer = 0;
        p->onwire = 0;
        p->ssp_event = 0;
        mutex_exit(&p->lock);

        if (mutex_tryenter(&pwp->wfree_lock) == 0) {
                mutex_enter(&pwp->pfree_lock);
                STAILQ_INSERT_TAIL(&pwp->pf, p, next);
                mutex_exit(&pwp->pfree_lock);
        } else {
                STAILQ_INSERT_TAIL(&pwp->wf, p, next);
                mutex_exit(&pwp->wfree_lock);
        }
}

/*
 * Find a work structure based upon a tag and make sure that the tag
 * serial number matches the work structure we've found.
 * If a structure is found, its lock is held upon return.
 * If lock_phy is B_TRUE, then lock the phy also when returning the work struct
 */
pmcwork_t *
pmcs_tag2wp(pmcs_hw_t *pwp, uint32_t htag, boolean_t lock_phy)
{
        pmcwork_t *p;
        pmcs_phy_t *phyp;
        uint32_t idx = PMCS_TAG_INDEX(htag);

        p = &pwp->work[idx];

        mutex_enter(&p->lock);
        if (p->htag == htag) {
                if (lock_phy) {
                        phyp = p->phy;
                        if (phyp != NULL) {
                                /* phy lock should be held before work lock */
                                mutex_exit(&p->lock);
                                mutex_enter(&phyp->phy_lock);
                                mutex_enter(&p->lock);
                        }
                        /*
                         * Check htag again, in case the work got completed
                         * while we dropped the work lock and got the phy lock
                         */
                        if (p->htag != htag) {
                                if (phyp != NULL) {
                                        mutex_exit(&p->lock);
                                        mutex_exit(&phyp->phy_lock);
                                }
                                pmcs_prt(pwp, PMCS_PRT_DEBUG, phyp, NULL, "%s: "
                                    "HTAG (0x%x) found, but work (0x%p) "
                                    "is already complete", __func__, htag,
                                    (void *)p);
                                return (NULL);
                        }
                }
                return (p);
        }
        mutex_exit(&p->lock);
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL,
            "INDEX 0x%x HTAG 0x%x got p->htag 0x%x", idx, htag, p->htag);
        return (NULL);
}

/*
 * Issue an abort for a command or for all commands.
 *
 * Since this can be called from interrupt context,
 * we don't wait for completion if wait is not set.
 *
 * Called with PHY lock held.
 */
int
pmcs_abort(pmcs_hw_t *pwp, pmcs_phy_t *pptr, uint32_t tag, int all_cmds,
    int wait)
{
        pmcwork_t *pwrk;
        pmcs_xscsi_t *tgt;
        uint32_t msg[PMCS_MSG_SIZE], *ptr;
        int result, abt_type;
        uint32_t abt_htag, status;

        if (pptr->abort_all_start) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL, "%s: ABORT_ALL for "
                    "(%s) already in progress.", __func__, pptr->path);
                return (EBUSY);
        }

        switch (pptr->dtype) {
        case SAS:
                abt_type = PMCIN_SSP_ABORT;
                break;
        case SATA:
                abt_type = PMCIN_SATA_ABORT;
                break;
        case EXPANDER:
                abt_type = PMCIN_SMP_ABORT;
                break;
        default:
                return (0);
        }

        pwrk = pmcs_gwork(pwp, wait ? PMCS_TAG_TYPE_WAIT : PMCS_TAG_TYPE_NONE,
            pptr);

        if (pwrk == NULL) {
                pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL, pmcs_nowrk, __func__);
                return (ENOMEM);
        }

        pwrk->dtype = pptr->dtype;
        pwrk->xp = pptr->target;
        pwrk->htag |= PMCS_TAG_NONIO_CMD;
        if (wait) {
                pwrk->arg = msg;
        }
        if (pptr->valid_device_id == 0) {
                pmcs_pwork(pwp, pwrk);
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: Invalid DeviceID", __func__);
                return (ENODEV);
        }
        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL, abt_type));
        msg[1] = LE_32(pwrk->htag);
        msg[2] = LE_32(pptr->device_id);
        if (all_cmds) {
                msg[3] = 0;
                msg[4] = LE_32(1);
                pwrk->ptr = NULL;
                pwrk->abt_htag = PMCS_ABT_HTAG_ALL;
                pptr->abort_all_start = gethrtime();
        } else {
                msg[3] = LE_32(tag);
                msg[4] = 0;
                pwrk->abt_htag = tag;
        }
        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (ptr == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_pwork(pwp, pwrk);
                pptr->abort_all_start = 0;
                pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL, pmcs_nomsg, __func__);
                return (ENOMEM);
        }

        COPY_MESSAGE(ptr, msg, 5);
        if (all_cmds) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: aborting all commands for %s device %s. (htag=0x%x)",
                    __func__, pmcs_get_typename(pptr->dtype), pptr->path,
                    msg[1]);
        } else {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                    "%s: aborting tag 0x%x for %s device %s. (htag=0x%x)",
                    __func__, tag, pmcs_get_typename(pptr->dtype), pptr->path,
                    msg[1]);
        }
        pwrk->state = PMCS_WORK_STATE_ONCHIP;

        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (!wait) {
                mutex_exit(&pwrk->lock);
                return (0);
        }

        abt_htag = pwrk->htag;
        pmcs_unlock_phy(pptr);
        WAIT_FOR(pwrk, 1000, result);
        pmcs_pwork(pwp, pwrk);
        pmcs_lock_phy(pptr);
        tgt = pptr->target;

        if (all_cmds) {
                pptr->abort_all_start = 0;
                cv_signal(&pptr->abort_all_cv);
        }

        if (result) {
                if (all_cmds) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt,
                            "%s: Abort all request timed out", __func__);
                } else {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt,
                            "%s: Abort (htag 0x%08x) request timed out",
                            __func__, abt_htag);
                }
                if (tgt != NULL) {
                        mutex_enter(&tgt->statlock);
                        if ((tgt->dev_state != PMCS_DEVICE_STATE_IN_RECOVERY) &&
                            (tgt->dev_state !=
                            PMCS_DEVICE_STATE_NON_OPERATIONAL)) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt,
                                    "%s: Trying DS error recovery for tgt 0x%p",
                                    __func__, (void *)tgt);
                                (void) pmcs_send_err_recovery_cmd(pwp,
                                    PMCS_DEVICE_STATE_IN_RECOVERY, pptr, tgt);
                        }
                        mutex_exit(&tgt->statlock);
                }
                return (ETIMEDOUT);
        }

        status = LE_32(msg[2]);
        if (status != PMCOUT_STATUS_OK) {
                /*
                 * The only non-success status are IO_NOT_VALID &
                 * IO_ABORT_IN_PROGRESS.
                 * In case of IO_ABORT_IN_PROGRESS, the other ABORT cmd's
                 * status is of concern and this duplicate cmd status can
                 * be ignored.
                 * If IO_NOT_VALID, that's not an error per-se.
                 * For abort of single I/O complete the command anyway.
                 * If, however, we were aborting all, that is a problem
                 * as IO_NOT_VALID really means that the IO or device is
                 * not there. So, discovery process will take of the cleanup.
                 */
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt,
                    "%s: abort result 0x%x", __func__, LE_32(msg[2]));
                if (all_cmds) {
                        PHY_CHANGED(pwp, pptr);
                        RESTART_DISCOVERY(pwp);
                } else {
                        return (EINVAL);
                }

                return (0);
        }

        if (tgt != NULL) {
                mutex_enter(&tgt->statlock);
                if (tgt->dev_state == PMCS_DEVICE_STATE_IN_RECOVERY) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt,
                            "%s: Restoring OPERATIONAL dev_state for tgt 0x%p",
                            __func__, (void *)tgt);
                        (void) pmcs_send_err_recovery_cmd(pwp,
                            PMCS_DEVICE_STATE_OPERATIONAL, pptr, tgt);
                }
                mutex_exit(&tgt->statlock);
        }

        return (0);
}

/*
 * Issue a task management function to an SSP device.
 *
 * Called with PHY lock held.
 * statlock CANNOT be held upon entry.
 */
int
pmcs_ssp_tmf(pmcs_hw_t *pwp, pmcs_phy_t *pptr, uint8_t tmf, uint32_t tag,
    uint64_t lun, uint32_t *response)
{
        int result, ds;
        uint8_t local[PMCS_QENTRY_SIZE << 1], *xd;
        sas_ssp_rsp_iu_t *rptr = (void *)local;
        static const uint8_t ssp_rsp_evec[] = {
                0x58, 0x61, 0x56, 0x72, 0x00
        };
        uint32_t msg[PMCS_MSG_SIZE], *ptr, status;
        struct pmcwork *pwrk;
        pmcs_xscsi_t *xp;

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr);
        if (pwrk == NULL) {
                pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL, pmcs_nowrk, __func__);
                return (ENOMEM);
        }
        /*
         * NB: We use the PMCS_OQ_GENERAL outbound queue
         * NB: so as to not get entangled in normal I/O
         * NB: processing.
         */
        pwrk->htag |= PMCS_TAG_NONIO_CMD;
        msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL,
            PMCIN_SSP_INI_TM_START));
        msg[1] = LE_32(pwrk->htag);
        msg[2] = LE_32(pptr->device_id);
        if (tmf == SAS_ABORT_TASK || tmf == SAS_QUERY_TASK) {
                msg[3] = LE_32(tag);
        } else {
                msg[3] = 0;
        }
        msg[4] = LE_32(tmf);
        msg[5] = BE_32((uint32_t)lun);
        msg[6] = BE_32((uint32_t)(lun >> 32));
        msg[7] = LE_32(PMCIN_MESSAGE_REPORT);

        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (ptr == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_pwork(pwp, pwrk);
                pmcs_prt(pwp, PMCS_PRT_ERR, pptr, NULL, pmcs_nomsg, __func__);
                return (ENOMEM);
        }
        COPY_MESSAGE(ptr, msg, 7);
        pwrk->arg = msg;
        pwrk->dtype = pptr->dtype;
        xp = pptr->target;
        pwrk->xp = xp;

        if (xp != NULL) {
                mutex_enter(&xp->statlock);
                if (xp->dev_state == PMCS_DEVICE_STATE_NON_OPERATIONAL) {
                        mutex_exit(&xp->statlock);
                        mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                        pmcs_pwork(pwp, pwrk);
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp, "%s: Not "
                            "sending '%s' because DS is '%s'", __func__,
                            pmcs_tmf2str(tmf), pmcs_status_str
                            (PMCOUT_STATUS_IO_DS_NON_OPERATIONAL));
                        return (EIO);
                }
                mutex_exit(&xp->statlock);
        }

        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
            "%s: sending '%s' to %s (lun %llu) tag 0x%x", __func__,
            pmcs_tmf2str(tmf), pptr->path, (unsigned long long) lun, tag);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        pmcs_unlock_phy(pptr);
        /*
         * This is a command sent to the target device, so it can take
         * significant amount of time to complete when path & device is busy.
         * Set a timeout to 20 seconds
         */
        WAIT_FOR(pwrk, 20000, result);
        pmcs_pwork(pwp, pwrk);
        pmcs_lock_phy(pptr);
        xp = pptr->target;

        if (result) {
                if (xp == NULL) {
                        return (ETIMEDOUT);
                }

                mutex_enter(&xp->statlock);
                pmcs_start_dev_state_recovery(xp, pptr);
                mutex_exit(&xp->statlock);
                return (ETIMEDOUT);
        }

        status = LE_32(msg[2]);
        if (status != PMCOUT_STATUS_OK) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: status %s for TMF %s action to %s, lun %llu",
                    __func__, pmcs_status_str(status),  pmcs_tmf2str(tmf),
                    pptr->path, (unsigned long long) lun);
                if ((status == PMCOUT_STATUS_IO_DS_NON_OPERATIONAL) ||
                    (status == PMCOUT_STATUS_OPEN_CNX_ERROR_BREAK) ||
                    (status == PMCOUT_STATUS_OPEN_CNX_ERROR_IT_NEXUS_LOSS)) {
                        ds = PMCS_DEVICE_STATE_NON_OPERATIONAL;
                } else if (status == PMCOUT_STATUS_IO_DS_IN_RECOVERY) {
                        /*
                         * If the status is IN_RECOVERY, it's an indication
                         * that it's now time for us to request to have the
                         * device state set to OPERATIONAL since we're the ones
                         * that requested recovery to begin with.
                         */
                        ds = PMCS_DEVICE_STATE_OPERATIONAL;
                } else {
                        ds = PMCS_DEVICE_STATE_IN_RECOVERY;
                }
                if (xp != NULL) {
                        mutex_enter(&xp->statlock);
                        if (xp->dev_state != ds) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                                    "%s: Sending err recovery cmd"
                                    " for tgt 0x%p (status = %s)",
                                    __func__, (void *)xp,
                                    pmcs_status_str(status));
                                (void) pmcs_send_err_recovery_cmd(pwp, ds,
                                    pptr, xp);
                        }
                        mutex_exit(&xp->statlock);
                }
                return (EIO);
        } else {
                ds = PMCS_DEVICE_STATE_OPERATIONAL;
                if (xp != NULL) {
                        mutex_enter(&xp->statlock);
                        if (xp->dev_state != ds) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                                    "%s: Sending err recovery cmd"
                                    " for tgt 0x%p (status = %s)",
                                    __func__, (void *)xp,
                                    pmcs_status_str(status));
                                (void) pmcs_send_err_recovery_cmd(pwp, ds,
                                    pptr, xp);
                        }
                        mutex_exit(&xp->statlock);
                }
        }
        if (LE_32(msg[3]) == 0) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "TMF completed with no response");
                return (EIO);
        }
        pmcs_endian_transform(pwp, local, &msg[5], ssp_rsp_evec);
        xd = (uint8_t *)(&msg[5]);
        xd += SAS_RSP_HDR_SIZE;
        if (rptr->datapres != SAS_RSP_DATAPRES_RESPONSE_DATA) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF response not RESPONSE DATA (0x%x)",
                    __func__, rptr->datapres);
                return (EIO);
        }
        if (rptr->response_data_length != 4) {
                pmcs_print_entry(pwp, PMCS_PRT_DEBUG,
                    "Bad SAS RESPONSE DATA LENGTH", msg);
                return (EIO);
        }
        (void) memcpy(&status, xd, sizeof (uint32_t));
        status = BE_32(status);
        if (response != NULL)
                *response = status;
        /*
         * The status is actually in the low-order byte.  The upper three
         * bytes contain additional information for the TMFs that support them.
         * However, at this time we do not issue any of those.  In the other
         * cases, the upper three bytes are supposed to be 0, but it appears
         * they aren't always.  Just mask them off.
         */
        switch (status & 0xff) {
        case SAS_RSP_TMF_COMPLETE:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF complete", __func__);
                result = 0;
                break;
        case SAS_RSP_TMF_SUCCEEDED:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF succeeded", __func__);
                result = 0;
                break;
        case SAS_RSP_INVALID_FRAME:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF returned INVALID FRAME", __func__);
                result = EIO;
                break;
        case SAS_RSP_TMF_NOT_SUPPORTED:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF returned TMF NOT SUPPORTED", __func__);
                result = EIO;
                break;
        case SAS_RSP_TMF_FAILED:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF returned TMF FAILED", __func__);
                result = EIO;
                break;
        case SAS_RSP_TMF_INCORRECT_LUN:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF returned INCORRECT LUN", __func__);
                result = EIO;
                break;
        case SAS_RSP_OVERLAPPED_OIPTTA:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF returned OVERLAPPED INITIATOR PORT TRANSFER TAG "
                    "ATTEMPTED", __func__);
                result = EIO;
                break;
        default:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
                    "%s: TMF returned unknown code 0x%x", __func__, status);
                result = EIO;
                break;
        }
        return (result);
}

/*
 * Called with PHY lock held and scratch acquired
 */
int
pmcs_sata_abort_ncq(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        const char *utag_fail_fmt = "%s: untagged NCQ command failure";
        const char *tag_fail_fmt = "%s: NCQ command failure (tag 0x%x)";
        uint32_t msg[PMCS_QENTRY_SIZE], *ptr, result, status;
        uint8_t *fp = pwp->scratch, ds;
        fis_t fis;
        pmcwork_t *pwrk;
        pmcs_xscsi_t *tgt;

        pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr);
        if (pwrk == NULL) {
                return (ENOMEM);
        }
        pwrk->htag |= PMCS_TAG_NONIO_CMD;
        msg[0] = LE_32(PMCS_IOMB_IN_SAS(PMCS_OQ_IODONE,
            PMCIN_SATA_HOST_IO_START));
        msg[1] = LE_32(pwrk->htag);
        msg[2] = LE_32(pptr->device_id);
        msg[3] = LE_32(512);
        msg[4] = LE_32(SATA_PROTOCOL_PIO | PMCIN_DATADIR_2_INI);
        msg[5] = LE_32((READ_LOG_EXT << 16) | (C_BIT << 8) | FIS_REG_H2DEV);
        msg[6] = LE_32(0x10);
        msg[8] = LE_32(1);
        msg[9] = 0;
        msg[10] = 0;
        msg[11] = 0;
        msg[12] = LE_32(DWORD0(pwp->scratch_dma));
        msg[13] = LE_32(DWORD1(pwp->scratch_dma));
        msg[14] = LE_32(512);
        msg[15] = 0;

        pwrk->arg = msg;
        pwrk->dtype = pptr->dtype;
        pwrk->xp = pptr->target;

        mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
        ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
        if (ptr == NULL) {
                mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                pmcs_pwork(pwp, pwrk);
                return (ENOMEM);
        }
        COPY_MESSAGE(ptr, msg, PMCS_QENTRY_SIZE);
        pwrk->state = PMCS_WORK_STATE_ONCHIP;
        INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

        pmcs_unlock_phy(pptr);
        WAIT_FOR(pwrk, 250, result);
        pmcs_pwork(pwp, pwrk);
        pmcs_lock_phy(pptr);

        tgt = pptr->target;
        if (result) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt, pmcs_timeo, __func__);
                return (EIO);
        }
        status = LE_32(msg[2]);
        if (status != PMCOUT_STATUS_OK || LE_32(msg[3])) {
                if (tgt == NULL) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt,
                            "%s: cannot find target for phy 0x%p for "
                            "dev state recovery", __func__, (void *)pptr);
                        return (EIO);
                }

                mutex_enter(&tgt->statlock);

                pmcs_print_entry(pwp, PMCS_PRT_DEBUG, "READ LOG EXT", msg);
                if ((status == PMCOUT_STATUS_IO_DS_NON_OPERATIONAL) ||
                    (status == PMCOUT_STATUS_OPEN_CNX_ERROR_BREAK) ||
                    (status == PMCOUT_STATUS_OPEN_CNX_ERROR_IT_NEXUS_LOSS)) {
                        ds = PMCS_DEVICE_STATE_NON_OPERATIONAL;
                } else {
                        ds = PMCS_DEVICE_STATE_IN_RECOVERY;
                }
                if (tgt->dev_state != ds) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt, "%s: Trying "
                            "SATA DS Recovery for tgt(0x%p) for status(%s)",
                            __func__, (void *)tgt, pmcs_status_str(status));
                        (void) pmcs_send_err_recovery_cmd(pwp, ds, pptr, tgt);
                }

                mutex_exit(&tgt->statlock);
                return (EIO);
        }
        fis[0] = (fp[4] << 24) | (fp[3] << 16) | (fp[2] << 8) | FIS_REG_D2H;
        fis[1] = (fp[8] << 24) | (fp[7] << 16) | (fp[6] << 8) | fp[5];
        fis[2] = (fp[12] << 24) | (fp[11] << 16) | (fp[10] << 8) | fp[9];
        fis[3] = (fp[16] << 24) | (fp[15] << 16) | (fp[14] << 8) | fp[13];
        fis[4] = 0;
        if (fp[0] & 0x80) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt,
                    utag_fail_fmt, __func__);
        } else {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, tgt,
                    tag_fail_fmt, __func__, fp[0] & 0x1f);
        }
        pmcs_fis_dump(pwp, fis);
        pptr->need_rl_ext = 0;
        return (0);
}

/*
 * Transform a structure from CPU to Device endian format, or
 * vice versa, based upon a transformation vector.
 *
 * A transformation vector is an array of bytes, each byte
 * of which is defined thusly:
 *
 *  bit 7: from CPU to desired endian, otherwise from desired endian
 *         to CPU format
 *  bit 6: Big Endian, else Little Endian
 *  bits 5-4:
 *       00 Undefined
 *       01 One Byte quantities
 *       02 Two Byte quantities
 *       03 Four Byte quantities
 *
 *  bits 3-0:
 *       00 Undefined
 *       Number of quantities to transform
 *
 * The vector is terminated by a 0 value.
 */

void
pmcs_endian_transform(pmcs_hw_t *pwp, void *orig_out, void *orig_in,
    const uint8_t *xfvec)
{
        uint8_t c, *out = orig_out, *in = orig_in;

        if (xfvec == NULL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: null xfvec", __func__);
                return;
        }
        if (out == NULL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: null out", __func__);
                return;
        }
        if (in == NULL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: null in", __func__);
                return;
        }
        while ((c = *xfvec++) != 0) {
                int nbyt = (c & 0xf);
                int size = (c >> 4) & 0x3;
                int bige = (c >> 4) & 0x4;

                switch (size) {
                case 1:
                {
                        while (nbyt-- > 0) {
                                *out++ = *in++;
                        }
                        break;
                }
                case 2:
                {
                        uint16_t tmp;
                        while (nbyt-- > 0) {
                                (void) memcpy(&tmp, in, sizeof (uint16_t));
                                if (bige) {
                                        tmp = BE_16(tmp);
                                } else {
                                        tmp = LE_16(tmp);
                                }
                                (void) memcpy(out, &tmp, sizeof (uint16_t));
                                out += sizeof (uint16_t);
                                in += sizeof (uint16_t);
                        }
                        break;
                }
                case 3:
                {
                        uint32_t tmp;
                        while (nbyt-- > 0) {
                                (void) memcpy(&tmp, in, sizeof (uint32_t));
                                if (bige) {
                                        tmp = BE_32(tmp);
                                } else {
                                        tmp = LE_32(tmp);
                                }
                                (void) memcpy(out, &tmp, sizeof (uint32_t));
                                out += sizeof (uint32_t);
                                in += sizeof (uint32_t);
                        }
                        break;
                }
                default:
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                            "%s: bad size", __func__);
                        return;
                }
        }
}

const char *
pmcs_get_rate(unsigned int linkrt)
{
        const char *rate;
        switch (linkrt) {
        case SAS_LINK_RATE_1_5GBIT:
                rate = "1.5";
                break;
        case SAS_LINK_RATE_3GBIT:
                rate = "3.0";
                break;
        case SAS_LINK_RATE_6GBIT:
                rate = "6.0";
                break;
        default:
                rate = "???";
                break;
        }
        return (rate);
}

const char *
pmcs_get_typename(pmcs_dtype_t type)
{
        switch (type) {
        case NOTHING:
                return ("NIL");
        case SATA:
                return ("SATA");
        case SAS:
                return ("SSP");
        case EXPANDER:
                return ("EXPANDER");
        }
        return ("????");
}

const char *
pmcs_tmf2str(int tmf)
{
        switch (tmf) {
        case SAS_ABORT_TASK:
                return ("Abort Task");
        case SAS_ABORT_TASK_SET:
                return ("Abort Task Set");
        case SAS_CLEAR_TASK_SET:
                return ("Clear Task Set");
        case SAS_LOGICAL_UNIT_RESET:
                return ("Logical Unit Reset");
        case SAS_I_T_NEXUS_RESET:
                return ("I_T Nexus Reset");
        case SAS_CLEAR_ACA:
                return ("Clear ACA");
        case SAS_QUERY_TASK:
                return ("Query Task");
        case SAS_QUERY_TASK_SET:
                return ("Query Task Set");
        case SAS_QUERY_UNIT_ATTENTION:
                return ("Query Unit Attention");
        default:
                return ("Unknown");
        }
}

const char *
pmcs_status_str(uint32_t status)
{
        switch (status) {
        case PMCOUT_STATUS_OK:
                return ("OK");
        case PMCOUT_STATUS_ABORTED:
                return ("ABORTED");
        case PMCOUT_STATUS_OVERFLOW:
                return ("OVERFLOW");
        case PMCOUT_STATUS_UNDERFLOW:
                return ("UNDERFLOW");
        case PMCOUT_STATUS_FAILED:
                return ("FAILED");
        case PMCOUT_STATUS_ABORT_RESET:
                return ("ABORT_RESET");
        case PMCOUT_STATUS_IO_NOT_VALID:
                return ("IO_NOT_VALID");
        case PMCOUT_STATUS_NO_DEVICE:
                return ("NO_DEVICE");
        case PMCOUT_STATUS_ILLEGAL_PARAMETER:
                return ("ILLEGAL_PARAMETER");
        case PMCOUT_STATUS_LINK_FAILURE:
                return ("LINK_FAILURE");
        case PMCOUT_STATUS_PROG_ERROR:
                return ("PROG_ERROR");
        case PMCOUT_STATUS_EDC_IN_ERROR:
                return ("EDC_IN_ERROR");
        case PMCOUT_STATUS_EDC_OUT_ERROR:
                return ("EDC_OUT_ERROR");
        case PMCOUT_STATUS_ERROR_HW_TIMEOUT:
                return ("ERROR_HW_TIMEOUT");
        case PMCOUT_STATUS_XFER_ERR_BREAK:
                return ("XFER_ERR_BREAK");
        case PMCOUT_STATUS_XFER_ERR_PHY_NOT_READY:
                return ("XFER_ERR_PHY_NOT_READY");
        case PMCOUT_STATUS_OPEN_CNX_PROTOCOL_NOT_SUPPORTED:
                return ("OPEN_CNX_PROTOCOL_NOT_SUPPORTED");
        case PMCOUT_STATUS_OPEN_CNX_ERROR_ZONE_VIOLATION:
                return ("OPEN_CNX_ERROR_ZONE_VIOLATION");
        case PMCOUT_STATUS_OPEN_CNX_ERROR_BREAK:
                return ("OPEN_CNX_ERROR_BREAK");
        case PMCOUT_STATUS_OPEN_CNX_ERROR_IT_NEXUS_LOSS:
                return ("OPEN_CNX_ERROR_IT_NEXUS_LOSS");
        case PMCOUT_STATUS_OPENCNX_ERROR_BAD_DESTINATION:
                return ("OPENCNX_ERROR_BAD_DESTINATION");
        case PMCOUT_STATUS_OPEN_CNX_ERROR_CONNECTION_RATE_NOT_SUPPORTED:
                return ("OPEN_CNX_ERROR_CONNECTION_RATE_NOT_SUPPORTED");
        case PMCOUT_STATUS_OPEN_CNX_ERROR_STP_RESOURCES_BUSY:
                return ("OPEN_CNX_ERROR_STP_RESOURCES_BUSY");
        case PMCOUT_STATUS_OPEN_CNX_ERROR_WRONG_DESTINATION:
                return ("OPEN_CNX_ERROR_WRONG_DESTINATION");
        case PMCOUT_STATUS_OPEN_CNX_ERROR_UNKNOWN_ERROR:
                return ("OPEN_CNX_ERROR_UNKNOWN_ERROR");
        case PMCOUT_STATUS_IO_XFER_ERROR_NAK_RECEIVED:
                return ("IO_XFER_ERROR_NAK_RECEIVED");
        case PMCOUT_STATUS_XFER_ERROR_ACK_NAK_TIMEOUT:
                return ("XFER_ERROR_ACK_NAK_TIMEOUT");
        case PMCOUT_STATUS_XFER_ERROR_PEER_ABORTED:
                return ("XFER_ERROR_PEER_ABORTED");
        case PMCOUT_STATUS_XFER_ERROR_RX_FRAME:
                return ("XFER_ERROR_RX_FRAME");
        case PMCOUT_STATUS_IO_XFER_ERROR_DMA:
                return ("IO_XFER_ERROR_DMA");
        case PMCOUT_STATUS_XFER_ERROR_CREDIT_TIMEOUT:
                return ("XFER_ERROR_CREDIT_TIMEOUT");
        case PMCOUT_STATUS_XFER_ERROR_SATA_LINK_TIMEOUT:
                return ("XFER_ERROR_SATA_LINK_TIMEOUT");
        case PMCOUT_STATUS_XFER_ERROR_SATA:
                return ("XFER_ERROR_SATA");
        case PMCOUT_STATUS_XFER_ERROR_REJECTED_NCQ_MODE:
                return ("XFER_ERROR_REJECTED_NCQ_MODE");
        case PMCOUT_STATUS_XFER_ERROR_ABORTED_DUE_TO_SRST:
                return ("XFER_ERROR_ABORTED_DUE_TO_SRST");
        case PMCOUT_STATUS_XFER_ERROR_ABORTED_NCQ_MODE:
                return ("XFER_ERROR_ABORTED_NCQ_MODE");
        case PMCOUT_STATUS_IO_XFER_OPEN_RETRY_TIMEOUT:
                return ("IO_XFER_OPEN_RETRY_TIMEOUT");
        case PMCOUT_STATUS_SMP_RESP_CONNECTION_ERROR:
                return ("SMP_RESP_CONNECTION_ERROR");
        case PMCOUT_STATUS_XFER_ERROR_UNEXPECTED_PHASE:
                return ("XFER_ERROR_UNEXPECTED_PHASE");
        case PMCOUT_STATUS_XFER_ERROR_RDY_OVERRUN:
                return ("XFER_ERROR_RDY_OVERRUN");
        case PMCOUT_STATUS_XFER_ERROR_RDY_NOT_EXPECTED:
                return ("XFER_ERROR_RDY_NOT_EXPECTED");
        case PMCOUT_STATUS_XFER_ERROR_CMD_ISSUE_ACK_NAK_TIMEOUT:
                return ("XFER_ERROR_CMD_ISSUE_ACK_NAK_TIMEOUT");
        case PMCOUT_STATUS_XFER_ERROR_CMD_ISSUE_BREAK_BEFORE_ACK_NACK:
                return ("XFER_ERROR_CMD_ISSUE_BREAK_BEFORE_ACK_NACK");
        case PMCOUT_STATUS_XFER_ERROR_CMD_ISSUE_PHY_DOWN_BEFORE_ACK_NAK:
                return ("XFER_ERROR_CMD_ISSUE_PHY_DOWN_BEFORE_ACK_NAK");
        case PMCOUT_STATUS_XFER_ERROR_OFFSET_MISMATCH:
                return ("XFER_ERROR_OFFSET_MISMATCH");
        case PMCOUT_STATUS_XFER_ERROR_ZERO_DATA_LEN:
                return ("XFER_ERROR_ZERO_DATA_LEN");
        case PMCOUT_STATUS_XFER_CMD_FRAME_ISSUED:
                return ("XFER_CMD_FRAME_ISSUED");
        case PMCOUT_STATUS_ERROR_INTERNAL_SMP_RESOURCE:
                return ("ERROR_INTERNAL_SMP_RESOURCE");
        case PMCOUT_STATUS_IO_PORT_IN_RESET:
                return ("IO_PORT_IN_RESET");
        case PMCOUT_STATUS_IO_DS_NON_OPERATIONAL:
                return ("DEVICE STATE NON-OPERATIONAL");
        case PMCOUT_STATUS_IO_DS_IN_RECOVERY:
                return ("DEVICE STATE IN RECOVERY");
        case PMCOUT_STATUS_IO_OPEN_CNX_ERROR_HW_RESOURCE_BUSY:
                return ("OPEN CNX ERR HW RESOURCE BUSY");
        default:
                return (NULL);
        }
}

uint64_t
pmcs_barray2wwn(uint8_t ba[8])
{
        uint64_t result = 0;
        int i;

        for (i = 0; i < 8; i++) {
                result <<= 8;
                result |= ba[i];
        }
        return (result);
}

void
pmcs_wwn2barray(uint64_t wwn, uint8_t ba[8])
{
        int i;
        for (i = 0; i < 8; i++) {
                ba[7 - i] = wwn & 0xff;
                wwn >>= 8;
        }
}

void
pmcs_report_fwversion(pmcs_hw_t *pwp)
{
        const char *fwsupport;
        switch (PMCS_FW_TYPE(pwp)) {
        case PMCS_FW_TYPE_RELEASED:
                fwsupport = "Released";
                break;
        case PMCS_FW_TYPE_DEVELOPMENT:
                fwsupport = "Development";
                break;
        case PMCS_FW_TYPE_ALPHA:
                fwsupport = "Alpha";
                break;
        case PMCS_FW_TYPE_BETA:
                fwsupport = "Beta";
                break;
        default:
                fwsupport = "Special";
                break;
        }
        pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL,
            "Chip Revision: %c; F/W Revision %x.%x.%x %s (ILA rev %08x)",
            'A' + pwp->chiprev, PMCS_FW_MAJOR(pwp), PMCS_FW_MINOR(pwp),
            PMCS_FW_MICRO(pwp), fwsupport, pwp->ila_ver);
}

void
pmcs_phy_name(pmcs_hw_t *pwp, pmcs_phy_t *pptr, char *obuf, size_t olen)
{
        if (pptr->parent) {
                pmcs_phy_name(pwp, pptr->parent, obuf, olen);
                (void) snprintf(obuf, olen, "%s.%02x", obuf, pptr->phynum);
        } else {
                (void) snprintf(obuf, olen, "pp%02x", pptr->phynum);
        }
}

/*
 * This function is called as a sanity check to ensure that a newly registered
 * PHY doesn't have a device_id that exists with another registered PHY.
 */
static boolean_t
pmcs_validate_devid(pmcs_phy_t *parent, pmcs_phy_t *phyp, uint32_t device_id)
{
        pmcs_phy_t *pptr, *pchild;
        boolean_t rval;

        pptr = parent;

        while (pptr) {
                if (pptr->valid_device_id && (pptr != phyp) &&
                    (pptr->device_id == device_id)) {
                        /*
                         * This can still be OK if both of these PHYs actually
                         * represent the same device (e.g. expander).  It could
                         * be a case of a new "primary" PHY.  If the SAS address
                         * is the same and they have the same parent, we'll
                         * accept this if the PHY to be registered is the
                         * primary.
                         */
                        if ((phyp->parent == pptr->parent) &&
                            (memcmp(phyp->sas_address,
                            pptr->sas_address, 8) == 0) && (phyp->width > 1)) {
                                /*
                                 * Move children over to the new primary and
                                 * update both PHYs
                                 */
                                pmcs_lock_phy(pptr);
                                phyp->children = pptr->children;
                                pchild = phyp->children;
                                while (pchild) {
                                        pchild->parent = phyp;
                                        pchild = pchild->sibling;
                                }
                                phyp->subsidiary = 0;
                                phyp->ncphy = pptr->ncphy;
                                /*
                                 * device_id, valid_device_id, and configured
                                 * will be set by the caller
                                 */
                                pptr->children = NULL;
                                pptr->subsidiary = 1;
                                pptr->ncphy = 0;
                                pmcs_unlock_phy(pptr);
                                pmcs_prt(pptr->pwp, PMCS_PRT_DEBUG, pptr, NULL,
                                    "%s: Moving device_id %d from PHY %s to %s",
                                    __func__, device_id, pptr->path,
                                    phyp->path);
                                return (B_TRUE);
                        }
                        pmcs_prt(pptr->pwp, PMCS_PRT_DEBUG, pptr, NULL,
                            "%s: phy %s already exists as %s with "
                            "device id 0x%x", __func__, phyp->path,
                            pptr->path, device_id);
                        return (B_FALSE);
                }

                if (pptr->children) {
                        rval = pmcs_validate_devid(pptr->children, phyp,
                            device_id);
                        if (rval == B_FALSE) {
                                return (rval);
                        }
                }

                pptr = pptr->sibling;
        }

        /* This PHY and device_id are valid */
        return (B_TRUE);
}

/*
 * If the PHY is found, it is returned locked
 */
static pmcs_phy_t *
pmcs_find_phy_by_wwn_impl(pmcs_phy_t *phyp, uint8_t *wwn)
{
        pmcs_phy_t *matched_phy, *cphyp, *nphyp;

        ASSERT(!mutex_owned(&phyp->phy_lock));

        while (phyp) {
                pmcs_lock_phy(phyp);

                if (phyp->valid_device_id) {
                        if (memcmp(phyp->sas_address, wwn, 8) == 0) {
                                return (phyp);
                        }
                }

                if (phyp->children) {
                        cphyp = phyp->children;
                        pmcs_unlock_phy(phyp);
                        matched_phy = pmcs_find_phy_by_wwn_impl(cphyp, wwn);
                        if (matched_phy) {
                                ASSERT(mutex_owned(&matched_phy->phy_lock));
                                return (matched_phy);
                        }
                        pmcs_lock_phy(phyp);
                }

                /*
                 * Only iterate through non-root PHYs
                 */
                if (IS_ROOT_PHY(phyp)) {
                        pmcs_unlock_phy(phyp);
                        phyp = NULL;
                } else {
                        nphyp = phyp->sibling;
                        pmcs_unlock_phy(phyp);
                        phyp = nphyp;
                }
        }

        return (NULL);
}

pmcs_phy_t *
pmcs_find_phy_by_wwn(pmcs_hw_t *pwp, uint64_t wwn)
{
        uint8_t ebstr[8];
        pmcs_phy_t *pptr, *matched_phy;

        pmcs_wwn2barray(wwn, ebstr);

        pptr = pwp->root_phys;
        while (pptr) {
                matched_phy = pmcs_find_phy_by_wwn_impl(pptr, ebstr);
                if (matched_phy) {
                        ASSERT(mutex_owned(&matched_phy->phy_lock));
                        return (matched_phy);
                }

                pptr = pptr->sibling;
        }

        return (NULL);
}


/*
 * pmcs_find_phy_by_sas_address
 *
 * Find a PHY that both matches "sas_addr" and is on "iport".
 * If a matching PHY is found, it is returned locked.
 */
pmcs_phy_t *
pmcs_find_phy_by_sas_address(pmcs_hw_t *pwp, pmcs_iport_t *iport,
    pmcs_phy_t *root, char *sas_addr)
{
        int ua_form = 1;
        uint64_t wwn;
        char addr[PMCS_MAX_UA_SIZE];
        pmcs_phy_t *pptr, *pnext, *pchild;

        if (root == NULL) {
                pptr = pwp->root_phys;
        } else {
                pptr = root;
        }

        while (pptr) {
                pmcs_lock_phy(pptr);
                /*
                 * If the PHY is dead or does not have a valid device ID,
                 * skip it.
                 */
                if ((pptr->dead) || (!pptr->valid_device_id)) {
                        goto next_phy;
                }

                if (pptr->iport != iport) {
                        goto next_phy;
                }

                wwn = pmcs_barray2wwn(pptr->sas_address);
                (void *) scsi_wwn_to_wwnstr(wwn, ua_form, addr);
                if (strncmp(addr, sas_addr, strlen(addr)) == 0) {
                        return (pptr);
                }

                if (pptr->children) {
                        pchild = pptr->children;
                        pmcs_unlock_phy(pptr);
                        pnext = pmcs_find_phy_by_sas_address(pwp, iport, pchild,
                            sas_addr);
                        if (pnext) {
                                return (pnext);
                        }
                        pmcs_lock_phy(pptr);
                }

next_phy:
                pnext = pptr->sibling;
                pmcs_unlock_phy(pptr);
                pptr = pnext;
        }

        return (NULL);
}

void
pmcs_fis_dump(pmcs_hw_t *pwp, fis_t fis)
{
        switch (fis[0] & 0xff) {
        case FIS_REG_H2DEV:
                pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL,
                    "FIS REGISTER HOST TO DEVICE: "
                    "OP=0x%02x Feature=0x%04x Count=0x%04x Device=0x%02x "
                    "LBA=%llu", BYTE2(fis[0]), BYTE3(fis[2]) << 8 |
                    BYTE3(fis[0]), WORD0(fis[3]), BYTE3(fis[1]),
                    (unsigned long long)
                    (((uint64_t)fis[2] & 0x00ffffff) << 24 |
                    ((uint64_t)fis[1] & 0x00ffffff)));
                break;
        case FIS_REG_D2H:
                pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL,
                    "FIS REGISTER DEVICE TO HOST: Status=0x%02x "
                    "Error=0x%02x Dev=0x%02x Count=0x%04x LBA=%llu",
                    BYTE2(fis[0]), BYTE3(fis[0]), BYTE3(fis[1]), WORD0(fis[3]),
                    (unsigned long long)(((uint64_t)fis[2] & 0x00ffffff) << 24 |
                    ((uint64_t)fis[1] & 0x00ffffff)));
                break;
        default:
                pmcs_prt(pwp, PMCS_PRT_INFO, NULL, NULL,
                    "FIS: 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x",
                    fis[0], fis[1], fis[2], fis[3], fis[4]);
                break;
        }
}

void
pmcs_print_entry(pmcs_hw_t *pwp, int level, char *msg, void *arg)
{
        uint32_t *mb = arg;
        size_t i;

        pmcs_prt(pwp, level, NULL, NULL, msg);
        for (i = 0; i < (PMCS_QENTRY_SIZE / sizeof (uint32_t)); i += 4) {
                pmcs_prt(pwp, level, NULL, NULL,
                    "Offset %2lu: 0x%08x 0x%08x 0x%08x 0x%08x",
                    i * sizeof (uint32_t), LE_32(mb[i]),
                    LE_32(mb[i+1]), LE_32(mb[i+2]), LE_32(mb[i+3]));
        }
}

/*
 * If phyp == NULL we're being called from the worker thread, in which
 * case we need to check all the PHYs.  In this case, the softstate lock
 * will be held.
 * If phyp is non-NULL, just issue the spinup release for the specified PHY
 * (which will already be locked).
 */
void
pmcs_spinup_release(pmcs_hw_t *pwp, pmcs_phy_t *phyp)
{
        uint32_t *msg;
        struct pmcwork *pwrk;
        pmcs_phy_t *tphyp;

        if (phyp != NULL) {
                ASSERT(mutex_owned(&phyp->phy_lock));
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, phyp, NULL,
                    "%s: Issuing spinup release only for PHY %s", __func__,
                    phyp->path);
                mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                msg = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
                if (msg == NULL || (pwrk =
                    pmcs_gwork(pwp, PMCS_TAG_TYPE_NONE, NULL)) == NULL) {
                        mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                        SCHEDULE_WORK(pwp, PMCS_WORK_SPINUP_RELEASE);
                        return;
                }

                phyp->spinup_hold = 0;
                bzero(msg, PMCS_QENTRY_SIZE);
                pwrk->htag |= PMCS_TAG_NONIO_CMD;
                msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL,
                    PMCIN_LOCAL_PHY_CONTROL));
                msg[1] = LE_32(pwrk->htag);
                msg[2] = LE_32((0x10 << 8) | phyp->phynum);

                pwrk->dtype = phyp->dtype;
                pwrk->state = PMCS_WORK_STATE_ONCHIP;
                pwrk->xp = phyp->target;
                mutex_exit(&pwrk->lock);
                INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
                return;
        }

        ASSERT(mutex_owned(&pwp->lock));

        tphyp = pwp->root_phys;
        while (tphyp) {
                pmcs_lock_phy(tphyp);
                if (tphyp->spinup_hold == 0) {
                        pmcs_unlock_phy(tphyp);
                        tphyp = tphyp->sibling;
                        continue;
                }

                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, tphyp, NULL,
                    "%s: Issuing spinup release for PHY %s", __func__,
                    tphyp->path);

                mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                msg = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
                if (msg == NULL || (pwrk =
                    pmcs_gwork(pwp, PMCS_TAG_TYPE_NONE, NULL)) == NULL) {
                        pmcs_unlock_phy(tphyp);
                        mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                        SCHEDULE_WORK(pwp, PMCS_WORK_SPINUP_RELEASE);
                        break;
                }

                tphyp->spinup_hold = 0;
                bzero(msg, PMCS_QENTRY_SIZE);
                msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL,
                    PMCIN_LOCAL_PHY_CONTROL));
                msg[1] = LE_32(pwrk->htag);
                msg[2] = LE_32((0x10 << 8) | tphyp->phynum);

                pwrk->dtype = tphyp->dtype;
                pwrk->state = PMCS_WORK_STATE_ONCHIP;
                pwrk->xp = tphyp->target;
                mutex_exit(&pwrk->lock);
                INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
                pmcs_unlock_phy(tphyp);

                tphyp = tphyp->sibling;
        }
}

/*
 * Abort commands on dead PHYs and deregister them as well as removing
 * the associated targets.
 */
static int
pmcs_kill_devices(pmcs_hw_t *pwp, pmcs_phy_t *phyp)
{
        pmcs_phy_t *pnext, *pchild;
        boolean_t remove_device;
        int rval = 0;

        while (phyp) {
                pmcs_lock_phy(phyp);
                pchild = phyp->children;
                pnext = phyp->sibling;
                pmcs_unlock_phy(phyp);

                if (pchild) {
                        rval = pmcs_kill_devices(pwp, pchild);
                        if (rval) {
                                return (rval);
                        }
                }

                mutex_enter(&pwp->lock);
                pmcs_lock_phy(phyp);
                if (phyp->dead && phyp->valid_device_id) {
                        remove_device = B_TRUE;
                } else {
                        remove_device = B_FALSE;
                }

                if (remove_device) {
                        pmcs_remove_device(pwp, phyp);
                        mutex_exit(&pwp->lock);

                        rval = pmcs_kill_device(pwp, phyp);
                        if (rval) {
                                pmcs_unlock_phy(phyp);
                                return (rval);
                        }
                } else {
                        mutex_exit(&pwp->lock);
                }

                pmcs_unlock_phy(phyp);
                phyp = pnext;
        }

        return (rval);
}

/*
 * Called with PHY locked
 */
int
pmcs_kill_device(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
        int rval;

        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL, "kill %s device @ %s",
            pmcs_get_typename(pptr->dtype), pptr->path);

        /*
         * There may be an outstanding ABORT_ALL running, which we wouldn't
         * know just by checking abort_pending.  We can, however, check
         * abort_all_start.  If it's non-zero, there is one, and we'll just
         * sit here and wait for it to complete.  If we don't, we'll remove
         * the device while there are still commands pending.
         */
        if (pptr->abort_all_start) {
                while (pptr->abort_all_start) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                            "%s: Waiting for outstanding ABORT_ALL on PHY 0x%p",
                            __func__, (void *)pptr);
                        cv_wait(&pptr->abort_all_cv, &pptr->phy_lock);
                }
        } else if (pptr->abort_pending) {
                rval = pmcs_abort(pwp, pptr, pptr->device_id, 1, 1);
                if (rval) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
                            "%s: ABORT_ALL returned non-zero status (%d) for "
                            "PHY 0x%p", __func__, rval, (void *)pptr);
                        return (rval);
                }
                pptr->abort_pending = 0;
        }

        if (pptr->valid_device_id) {
                pmcs_deregister_device(pwp, pptr);
        }

        PHY_CHANGED(pwp, pptr);
        RESTART_DISCOVERY(pwp);
        pptr->valid_device_id = 0;
        return (0);
}

/*
 * Acknowledge the SAS h/w events that need acknowledgement.
 * This is only needed for first level PHYs.
 */
void
pmcs_ack_events(pmcs_hw_t *pwp)
{
        uint32_t msg[PMCS_MSG_SIZE], *ptr;
        struct pmcwork *pwrk;
        pmcs_phy_t *pptr;

        for (pptr = pwp->root_phys; pptr; pptr = pptr->sibling) {
                pmcs_lock_phy(pptr);
                if (pptr->hw_event_ack == 0) {
                        pmcs_unlock_phy(pptr);
                        continue;
                }
                mutex_enter(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                ptr = GET_IQ_ENTRY(pwp, PMCS_IQ_OTHER);

                if ((ptr == NULL) || (pwrk =
                    pmcs_gwork(pwp, PMCS_TAG_TYPE_NONE, NULL)) == NULL) {
                        mutex_exit(&pwp->iqp_lock[PMCS_IQ_OTHER]);
                        pmcs_unlock_phy(pptr);
                        SCHEDULE_WORK(pwp, PMCS_WORK_SAS_HW_ACK);
                        break;
                }

                msg[0] = LE_32(PMCS_HIPRI(pwp, PMCS_OQ_GENERAL,
                    PMCIN_SAS_HW_EVENT_ACK));
                msg[1] = LE_32(pwrk->htag);
                msg[2] = LE_32(pptr->hw_event_ack);

                mutex_exit(&pwrk->lock);
                pwrk->dtype = pptr->dtype;
                pptr->hw_event_ack = 0;
                COPY_MESSAGE(ptr, msg, 3);
                INC_IQ_ENTRY(pwp, PMCS_IQ_OTHER);
                pmcs_unlock_phy(pptr);
        }
}

/*
 * Load DMA
 */
int
pmcs_dma_load(pmcs_hw_t *pwp, pmcs_cmd_t *sp, uint32_t *msg)
{
        ddi_dma_cookie_t *sg;
        pmcs_dmachunk_t *tc;
        pmcs_dmasgl_t *sgl, *prior;
        int seg, tsc;
        uint64_t sgl_addr;

        /*
         * If we have no data segments, we're done.
         */
        if (CMD2PKT(sp)->pkt_numcookies == 0) {
                return (0);
        }

        /*
         * Get the S/G list pointer.
         */
        sg = CMD2PKT(sp)->pkt_cookies;

        /*
         * If we only have one dma segment, we can directly address that
         * data within the Inbound message itself.
         */
        if (CMD2PKT(sp)->pkt_numcookies == 1) {
                msg[12] = LE_32(DWORD0(sg->dmac_laddress));
                msg[13] = LE_32(DWORD1(sg->dmac_laddress));
                msg[14] = LE_32(sg->dmac_size);
                msg[15] = 0;
                return (0);
        }

        /*
         * Otherwise, we'll need one or more external S/G list chunks.
         * Get the first one and its dma address into the Inbound message.
         */
        mutex_enter(&pwp->dma_lock);
        tc = pwp->dma_freelist;
        if (tc == NULL) {
                SCHEDULE_WORK(pwp, PMCS_WORK_ADD_DMA_CHUNKS);
                mutex_exit(&pwp->dma_lock);
                pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL,
                    "%s: out of SG lists", __func__);
                return (-1);
        }
        pwp->dma_freelist = tc->nxt;
        mutex_exit(&pwp->dma_lock);

        tc->nxt = NULL;
        sp->cmd_clist = tc;
        sgl = tc->chunks;
        (void) memset(tc->chunks, 0, PMCS_SGL_CHUNKSZ);
        sgl_addr = tc->addr;
        msg[12] = LE_32(DWORD0(sgl_addr));
        msg[13] = LE_32(DWORD1(sgl_addr));
        msg[14] = 0;
        msg[15] = LE_32(PMCS_DMASGL_EXTENSION);

        prior = sgl;
        tsc = 0;

        for (seg = 0; seg < CMD2PKT(sp)->pkt_numcookies; seg++) {
                /*
                 * If the current segment count for this chunk is one less than
                 * the number s/g lists per chunk and we have more than one seg
                 * to go, we need another chunk. Get it, and make sure that the
                 * tail end of the the previous chunk points the new chunk
                 * (if remembering an offset can be called 'pointing to').
                 *
                 * Note that we can store the offset into our command area that
                 * represents the new chunk in the length field of the part
                 * that points the PMC chip at the next chunk- the PMC chip
                 * ignores this field when the EXTENSION bit is set.
                 *
                 * This is required for dma unloads later.
                 */
                if (tsc == (PMCS_SGL_NCHUNKS - 1) &&
                    seg < (CMD2PKT(sp)->pkt_numcookies - 1)) {
                        mutex_enter(&pwp->dma_lock);
                        tc = pwp->dma_freelist;
                        if (tc == NULL) {
                                SCHEDULE_WORK(pwp, PMCS_WORK_ADD_DMA_CHUNKS);
                                mutex_exit(&pwp->dma_lock);
                                pmcs_dma_unload(pwp, sp);
                                pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL,
                                    "%s: out of SG lists", __func__);
                                return (-1);
                        }
                        pwp->dma_freelist = tc->nxt;
                        tc->nxt = sp->cmd_clist;
                        mutex_exit(&pwp->dma_lock);

                        sp->cmd_clist = tc;
                        (void) memset(tc->chunks, 0, PMCS_SGL_CHUNKSZ);
                        sgl = tc->chunks;
                        sgl_addr = tc->addr;
                        prior[PMCS_SGL_NCHUNKS-1].sglal =
                            LE_32(DWORD0(sgl_addr));
                        prior[PMCS_SGL_NCHUNKS-1].sglah =
                            LE_32(DWORD1(sgl_addr));
                        prior[PMCS_SGL_NCHUNKS-1].sglen = 0;
                        prior[PMCS_SGL_NCHUNKS-1].flags =
                            LE_32(PMCS_DMASGL_EXTENSION);
                        prior = sgl;
                        tsc = 0;
                }
                sgl[tsc].sglal = LE_32(DWORD0(sg->dmac_laddress));
                sgl[tsc].sglah = LE_32(DWORD1(sg->dmac_laddress));
                sgl[tsc].sglen = LE_32(sg->dmac_size);
                sgl[tsc++].flags = 0;
                sg++;
        }
        return (0);
}

/*
 * Unload DMA
 */
void
pmcs_dma_unload(pmcs_hw_t *pwp, pmcs_cmd_t *sp)
{
        pmcs_dmachunk_t *cp;

        mutex_enter(&pwp->dma_lock);
        while ((cp = sp->cmd_clist) != NULL) {
                sp->cmd_clist = cp->nxt;
                cp->nxt = pwp->dma_freelist;
                pwp->dma_freelist = cp;
        }
        mutex_exit(&pwp->dma_lock);
}

/*
 * Take a chunk of consistent memory that has just been allocated and inserted
 * into the cip indices and prepare it for DMA chunk usage and add it to the
 * freelist.
 *
 * Called with dma_lock locked (except during attach when it's unnecessary)
 */
void
pmcs_idma_chunks(pmcs_hw_t *pwp, pmcs_dmachunk_t *dcp,
    pmcs_chunk_t *pchunk, unsigned long lim)
{
        unsigned long off, n;
        pmcs_dmachunk_t *np = dcp;
        pmcs_chunk_t *tmp_chunk;

        if (pwp->dma_chunklist == NULL) {
                pwp->dma_chunklist = pchunk;
        } else {
                tmp_chunk = pwp->dma_chunklist;
                while (tmp_chunk->next) {
                        tmp_chunk = tmp_chunk->next;
                }
                tmp_chunk->next = pchunk;
        }

        /*
         * Install offsets into chunk lists.
         */
        for (n = 0, off = 0; off < lim; off += PMCS_SGL_CHUNKSZ, n++) {
                np->chunks = (void *)&pchunk->addrp[off];
                np->addr = pchunk->dma_addr + off;
                np->acc_handle = pchunk->acc_handle;
                np->dma_handle = pchunk->dma_handle;
                if ((off + PMCS_SGL_CHUNKSZ) < lim) {
                        np = np->nxt;
                }
        }
        np->nxt = pwp->dma_freelist;
        pwp->dma_freelist = dcp;
        pmcs_prt(pwp, PMCS_PRT_DEBUG2, NULL, NULL,
            "added %lu DMA chunks ", n);
}

/*
 * Change the value of the interrupt coalescing timer.  This is done currently
 * only for I/O completions.  If we're using the "auto clear" feature, it can
 * be turned back on when interrupt coalescing is turned off and must be
 * turned off when the coalescing timer is on.
 * NOTE: PMCS_MSIX_GENERAL and PMCS_OQ_IODONE are the same value.  As long
 * as that's true, we don't need to distinguish between them.
 */

void
pmcs_set_intr_coal_timer(pmcs_hw_t *pwp, pmcs_coal_timer_adj_t adj)
{
        if (adj == DECREASE_TIMER) {
                /* If the timer is already off, nothing to do. */
                if (pwp->io_intr_coal.timer_on == B_FALSE) {
                        return;
                }

                pwp->io_intr_coal.intr_coal_timer -= PMCS_COAL_TIMER_GRAN;

                if (pwp->io_intr_coal.intr_coal_timer == 0) {
                        /* Disable the timer */
                        pmcs_wr_topunit(pwp, PMCS_INT_COALESCING_CONTROL, 0);

                        if (pwp->odb_auto_clear & (1 << PMCS_MSIX_IODONE)) {
                                pmcs_wr_topunit(pwp, PMCS_OBDB_AUTO_CLR,
                                    pwp->odb_auto_clear);
                        }

                        pwp->io_intr_coal.timer_on = B_FALSE;
                        pwp->io_intr_coal.max_io_completions = B_FALSE;
                        pwp->io_intr_coal.num_intrs = 0;
                        pwp->io_intr_coal.int_cleared = B_FALSE;
                        pwp->io_intr_coal.num_io_completions = 0;

                        DTRACE_PROBE1(pmcs__intr__coalesce__timer__off,
                            pmcs_io_intr_coal_t *, &pwp->io_intr_coal);
                } else {
                        pmcs_wr_topunit(pwp, PMCS_INT_COALESCING_TIMER,
                            pwp->io_intr_coal.intr_coal_timer);
                }
        } else {
                /*
                 * If the timer isn't on yet, do the setup for it now.
                 */
                if (pwp->io_intr_coal.timer_on == B_FALSE) {
                        /* If auto clear is being used, turn it off. */
                        if (pwp->odb_auto_clear & (1 << PMCS_MSIX_IODONE)) {
                                pmcs_wr_topunit(pwp, PMCS_OBDB_AUTO_CLR,
                                    (pwp->odb_auto_clear &
                                    ~(1 << PMCS_MSIX_IODONE)));
                        }

                        pmcs_wr_topunit(pwp, PMCS_INT_COALESCING_CONTROL,
                            (1 << PMCS_MSIX_IODONE));
                        pwp->io_intr_coal.timer_on = B_TRUE;
                        pwp->io_intr_coal.intr_coal_timer =
                            PMCS_COAL_TIMER_GRAN;

                        DTRACE_PROBE1(pmcs__intr__coalesce__timer__on,
                            pmcs_io_intr_coal_t *, &pwp->io_intr_coal);
                } else {
                        pwp->io_intr_coal.intr_coal_timer +=
                            PMCS_COAL_TIMER_GRAN;
                }

                if (pwp->io_intr_coal.intr_coal_timer > PMCS_MAX_COAL_TIMER) {
                        pwp->io_intr_coal.intr_coal_timer = PMCS_MAX_COAL_TIMER;
                }

                pmcs_wr_topunit(pwp, PMCS_INT_COALESCING_TIMER,
                    pwp->io_intr_coal.intr_coal_timer);
        }

        /*
         * Adjust the interrupt threshold based on the current timer value
         */
        pwp->io_intr_coal.intr_threshold =
            PMCS_INTR_THRESHOLD(PMCS_QUANTUM_TIME_USECS * 1000 /
            (pwp->io_intr_coal.intr_latency +
            (pwp->io_intr_coal.intr_coal_timer * 1000)));
}

/*
 * Register Access functions
 */
uint32_t
pmcs_rd_iqci(pmcs_hw_t *pwp, uint32_t qnum)
{
        uint32_t iqci;

        if (ddi_dma_sync(pwp->cip_handles, 0, 0, DDI_DMA_SYNC_FORKERNEL) !=
            DDI_SUCCESS) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: ddi_dma_sync failed?", __func__);
        }

        iqci = LE_32(
            ((uint32_t *)((void *)pwp->cip))[IQ_OFFSET(qnum) >> 2]);

        return (iqci);
}

uint32_t
pmcs_rd_oqpi(pmcs_hw_t *pwp, uint32_t qnum)
{
        uint32_t oqpi;

        if (ddi_dma_sync(pwp->cip_handles, 0, 0, DDI_DMA_SYNC_FORKERNEL) !=
            DDI_SUCCESS) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: ddi_dma_sync failed?", __func__);
        }

        oqpi = LE_32(
            ((uint32_t *)((void *)pwp->cip))[OQ_OFFSET(qnum) >> 2]);

        return (oqpi);
}

uint32_t
pmcs_rd_gsm_reg(pmcs_hw_t *pwp, uint8_t hi, uint32_t off)
{
        uint32_t rv, newaxil, oldaxil, oldaxih;

        newaxil = off & ~GSM_BASE_MASK;
        off &= GSM_BASE_MASK;
        mutex_enter(&pwp->axil_lock);
        oldaxil = ddi_get32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2]);
        ddi_put32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2], newaxil);
        drv_usecwait(10);
        if (ddi_get32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2]) != newaxil) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "AXIL register update failed");
        }
        if (hi) {
                oldaxih = ddi_get32(pwp->top_acc_handle,
                    &pwp->top_regs[PMCS_AXI_TRANS_UPPER >> 2]);
                ddi_put32(pwp->top_acc_handle,
                    &pwp->top_regs[PMCS_AXI_TRANS_UPPER >> 2], hi);
                drv_usecwait(10);
                if (ddi_get32(pwp->top_acc_handle,
                    &pwp->top_regs[PMCS_AXI_TRANS_UPPER >> 2]) != hi) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                            "AXIH register update failed");
                }
        }
        rv = ddi_get32(pwp->gsm_acc_handle, &pwp->gsm_regs[off >> 2]);
        if (hi) {
                ddi_put32(pwp->top_acc_handle,
                    &pwp->top_regs[PMCS_AXI_TRANS_UPPER >> 2], oldaxih);
                drv_usecwait(10);
                if (ddi_get32(pwp->top_acc_handle,
                    &pwp->top_regs[PMCS_AXI_TRANS_UPPER >> 2]) != oldaxih) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                            "AXIH register restore failed");
                }
        }
        ddi_put32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2], oldaxil);
        drv_usecwait(10);
        if (ddi_get32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2]) != oldaxil) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "AXIL register restore failed");
        }
        mutex_exit(&pwp->axil_lock);
        return (rv);
}

void
pmcs_wr_gsm_reg(pmcs_hw_t *pwp, uint32_t off, uint32_t val)
{
        uint32_t newaxil, oldaxil;

        newaxil = off & ~GSM_BASE_MASK;
        off &= GSM_BASE_MASK;
        mutex_enter(&pwp->axil_lock);
        oldaxil = ddi_get32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2]);
        ddi_put32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2], newaxil);
        drv_usecwait(10);
        if (ddi_get32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2]) != newaxil) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "AXIL register update failed");
        }
        ddi_put32(pwp->gsm_acc_handle, &pwp->gsm_regs[off >> 2], val);
        ddi_put32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2], oldaxil);
        drv_usecwait(10);
        if (ddi_get32(pwp->top_acc_handle,
            &pwp->top_regs[PMCS_AXI_TRANS >> 2]) != oldaxil) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "AXIL register restore failed");
        }
        mutex_exit(&pwp->axil_lock);
}

uint32_t
pmcs_rd_topunit(pmcs_hw_t *pwp, uint32_t off)
{
        switch (off) {
        case PMCS_SPC_RESET:
        case PMCS_SPC_BOOT_STRAP:
        case PMCS_SPC_DEVICE_ID:
        case PMCS_DEVICE_REVISION:
                off = pmcs_rd_gsm_reg(pwp, 0, off);
                break;
        default:
                off = ddi_get32(pwp->top_acc_handle,
                    &pwp->top_regs[off >> 2]);
                break;
        }
        return (off);
}

void
pmcs_wr_topunit(pmcs_hw_t *pwp, uint32_t off, uint32_t val)
{
        switch (off) {
        case PMCS_SPC_RESET:
        case PMCS_DEVICE_REVISION:
                pmcs_wr_gsm_reg(pwp, off, val);
                break;
        default:
                ddi_put32(pwp->top_acc_handle, &pwp->top_regs[off >> 2], val);
                break;
        }
}

uint32_t
pmcs_rd_msgunit(pmcs_hw_t *pwp, uint32_t off)
{
        return (ddi_get32(pwp->msg_acc_handle, &pwp->msg_regs[off >> 2]));
}

uint32_t
pmcs_rd_mpi_tbl(pmcs_hw_t *pwp, uint32_t off)
{
        return (ddi_get32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[(pwp->mpi_offset + off) >> 2]));
}

uint32_t
pmcs_rd_gst_tbl(pmcs_hw_t *pwp, uint32_t off)
{
        return (ddi_get32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[(pwp->mpi_gst_offset + off) >> 2]));
}

uint32_t
pmcs_rd_iqc_tbl(pmcs_hw_t *pwp, uint32_t off)
{
        return (ddi_get32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[(pwp->mpi_iqc_offset + off) >> 2]));
}

uint32_t
pmcs_rd_oqc_tbl(pmcs_hw_t *pwp, uint32_t off)
{
        return (ddi_get32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[(pwp->mpi_oqc_offset + off) >> 2]));
}

uint32_t
pmcs_rd_iqpi(pmcs_hw_t *pwp, uint32_t qnum)
{
        return (ddi_get32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[pwp->iqpi_offset[qnum] >> 2]));
}

uint32_t
pmcs_rd_oqci(pmcs_hw_t *pwp, uint32_t qnum)
{
        return (ddi_get32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[pwp->oqci_offset[qnum] >> 2]));
}

void
pmcs_wr_msgunit(pmcs_hw_t *pwp, uint32_t off, uint32_t val)
{
        ddi_put32(pwp->msg_acc_handle, &pwp->msg_regs[off >> 2], val);
}

void
pmcs_wr_mpi_tbl(pmcs_hw_t *pwp, uint32_t off, uint32_t val)
{
        ddi_put32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[(pwp->mpi_offset + off) >> 2], (val));
}

void
pmcs_wr_gst_tbl(pmcs_hw_t *pwp, uint32_t off, uint32_t val)
{
        ddi_put32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[(pwp->mpi_gst_offset + off) >> 2], val);
}

void
pmcs_wr_iqc_tbl(pmcs_hw_t *pwp, uint32_t off, uint32_t val)
{
        ddi_put32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[(pwp->mpi_iqc_offset + off) >> 2], val);
}

void
pmcs_wr_oqc_tbl(pmcs_hw_t *pwp, uint32_t off, uint32_t val)
{
        ddi_put32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[(pwp->mpi_oqc_offset + off) >> 2], val);
}

void
pmcs_wr_iqci(pmcs_hw_t *pwp, uint32_t qnum, uint32_t val)
{
        ((uint32_t *)((void *)pwp->cip))[IQ_OFFSET(qnum) >> 2] = val;
        if (ddi_dma_sync(pwp->cip_handles, 0, 0, DDI_DMA_SYNC_FORDEV) !=
            DDI_SUCCESS) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: ddi_dma_sync failed?", __func__);
        }
}

void
pmcs_wr_iqpi(pmcs_hw_t *pwp, uint32_t qnum, uint32_t val)
{
        ddi_put32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[pwp->iqpi_offset[qnum] >> 2], val);
}

void
pmcs_wr_oqci(pmcs_hw_t *pwp, uint32_t qnum, uint32_t val)
{
        ddi_put32(pwp->mpi_acc_handle,
            &pwp->mpi_regs[pwp->oqci_offset[qnum] >> 2], val);
}

void
pmcs_wr_oqpi(pmcs_hw_t *pwp, uint32_t qnum, uint32_t val)
{
        ((uint32_t *)((void *)pwp->cip))[OQ_OFFSET(qnum) >> 2] = val;
        if (ddi_dma_sync(pwp->cip_handles, 0, 0, DDI_DMA_SYNC_FORDEV) !=
            DDI_SUCCESS) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: ddi_dma_sync failed?", __func__);
        }
}

/*
 * Check the status value of an outbound IOMB and report anything bad
 */

void
pmcs_check_iomb_status(pmcs_hw_t *pwp, uint32_t *iomb)
{
        uint16_t        opcode;
        int             offset;

        if (iomb == NULL) {
                return;
        }

        opcode = LE_32(iomb[0]) & 0xfff;

        switch (opcode) {
                /*
                 * The following have no status field, so ignore them
                 */
        case PMCOUT_ECHO:
        case PMCOUT_SAS_HW_EVENT:
        case PMCOUT_GET_DEVICE_HANDLE:
        case PMCOUT_SATA_EVENT:
        case PMCOUT_SSP_EVENT:
        case PMCOUT_DEVICE_HANDLE_ARRIVED:
        case PMCOUT_GPIO:
        case PMCOUT_GPIO_EVENT:
        case PMCOUT_GET_TIME_STAMP:
        case PMCOUT_SKIP_ENTRIES:
        case PMCOUT_GET_NVMD_DATA:      /* Actually lower 16 bits of word 3 */
        case PMCOUT_SET_NVMD_DATA:      /* but ignore - we don't use these */
        case PMCOUT_DEVICE_HANDLE_REMOVED:
        case PMCOUT_SSP_REQUEST_RECEIVED:
                return;

        case PMCOUT_GENERAL_EVENT:
                offset = 1;
                break;

        case PMCOUT_SSP_COMPLETION:
        case PMCOUT_SMP_COMPLETION:
        case PMCOUT_DEVICE_REGISTRATION:
        case PMCOUT_DEREGISTER_DEVICE_HANDLE:
        case PMCOUT_SATA_COMPLETION:
        case PMCOUT_DEVICE_INFO:
        case PMCOUT_FW_FLASH_UPDATE:
        case PMCOUT_SSP_ABORT:
        case PMCOUT_SATA_ABORT:
        case PMCOUT_SAS_DIAG_MODE_START_END:
        case PMCOUT_SAS_HW_EVENT_ACK_ACK:
        case PMCOUT_SMP_ABORT:
        case PMCOUT_SET_DEVICE_STATE:
        case PMCOUT_GET_DEVICE_STATE:
        case PMCOUT_SET_DEVICE_INFO:
                offset = 2;
                break;

        case PMCOUT_LOCAL_PHY_CONTROL:
        case PMCOUT_SAS_DIAG_EXECUTE:
        case PMCOUT_PORT_CONTROL:
                offset = 3;
                break;

        case PMCOUT_GET_INFO:
        case PMCOUT_GET_VPD:
        case PMCOUT_SAS_ASSISTED_DISCOVERY_EVENT:
        case PMCOUT_SATA_ASSISTED_DISCOVERY_EVENT:
        case PMCOUT_SET_VPD:
        case PMCOUT_TWI:
                pmcs_print_entry(pwp, PMCS_PRT_DEBUG,
                    "Got response for deprecated opcode", iomb);
                return;

        default:
                pmcs_print_entry(pwp, PMCS_PRT_DEBUG,
                    "Got response for unknown opcode", iomb);
                return;
        }

        if (LE_32(iomb[offset]) != PMCOUT_STATUS_OK) {
                pmcs_print_entry(pwp, PMCS_PRT_DEBUG,
                    "bad status on TAG_TYPE_NONE command", iomb);
        }
}

/*
 * Called with statlock held
 */
void
pmcs_clear_xp(pmcs_hw_t *pwp, pmcs_xscsi_t *xp)
{
        _NOTE(ARGUNUSED(pwp));

        ASSERT(mutex_owned(&xp->statlock));

        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, xp, "%s: Device 0x%p is gone.",
            __func__, (void *)xp);

        xp->special_running = 0;
        xp->recovering = 0;
        xp->recover_wait = 0;
        xp->draining = 0;
        xp->new = 0;
        xp->assigned = 0;
        xp->dev_state = 0;
        xp->tagmap = 0;
        xp->dev_gone = 1;
        xp->event_recovery = 0;
        xp->dtype = NOTHING;
        xp->wq_recovery_tail = NULL;
        /* Don't clear xp->phy */
        /* Don't clear xp->actv_cnt */
        /* Don't clear xp->actv_pkts */

        /*
         * Flush all target queues
         */
        pmcs_flush_target_queues(pwp, xp, PMCS_TGT_ALL_QUEUES);
}

static int
pmcs_smp_function_result(pmcs_hw_t *pwp, smp_response_frame_t *srf)
{
        int result = srf->srf_result;

        switch (result) {
        case SMP_RES_UNKNOWN_FUNCTION:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: SMP DISCOVER Response "
                    "Function Result: Unknown SMP Function(0x%x)",
                    __func__, result);
                break;
        case SMP_RES_FUNCTION_FAILED:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: SMP DISCOVER Response "
                    "Function Result: SMP Function Failed(0x%x)",
                    __func__, result);
                break;
        case SMP_RES_INVALID_REQUEST_FRAME_LENGTH:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: SMP DISCOVER Response "
                    "Function Result: Invalid Request Frame Length(0x%x)",
                    __func__, result);
                break;
        case SMP_RES_INCOMPLETE_DESCRIPTOR_LIST:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: SMP DISCOVER Response "
                    "Function Result: Incomplete Descriptor List(0x%x)",
                    __func__, result);
                break;
        case SMP_RES_PHY_DOES_NOT_EXIST:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: SMP DISCOVER Response "
                    "Function Result: PHY does not exist(0x%x)",
                    __func__, result);
                break;
        case SMP_RES_PHY_VACANT:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: SMP DISCOVER Response "
                    "Function Result: PHY Vacant(0x%x)",
                    __func__, result);
                break;
        default:
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: SMP DISCOVER Response "
                    "Function Result: (0x%x)",
                    __func__, result);
                break;
        }

        return (result);
}

/*
 * Do all the repetitive stuff necessary to setup for DMA
 *
 * pwp: Used for dip
 * dma_attr: ddi_dma_attr_t to use for the mapping
 * acch: ddi_acc_handle_t to use for the mapping
 * dmah: ddi_dma_handle_t to use
 * length: Amount of memory for mapping
 * kvap: Pointer filled in with kernel virtual address on successful return
 * dma_addr: Pointer filled in with DMA address on successful return
 */
boolean_t
pmcs_dma_setup(pmcs_hw_t *pwp, ddi_dma_attr_t *dma_attr, ddi_acc_handle_t *acch,
    ddi_dma_handle_t *dmah, size_t length, caddr_t *kvap, uint64_t *dma_addr)
{
        dev_info_t              *dip = pwp->dip;
        ddi_dma_cookie_t        cookie;
        size_t                  real_length;
        uint_t                  ddma_flag = DDI_DMA_CONSISTENT;
        uint_t                  ddabh_flag = DDI_DMA_CONSISTENT | DDI_DMA_RDWR;
        uint_t                  cookie_cnt;
        ddi_device_acc_attr_t   mattr = {
                DDI_DEVICE_ATTR_V0,
                DDI_NEVERSWAP_ACC,
                DDI_STRICTORDER_ACC,
                DDI_DEFAULT_ACC
        };

        *acch = NULL;
        *dmah = NULL;

        if (ddi_dma_alloc_handle(dip, dma_attr, DDI_DMA_SLEEP, NULL, dmah) !=
            DDI_SUCCESS) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "Failed to allocate DMA handle");
                return (B_FALSE);
        }

        if (ddi_dma_mem_alloc(*dmah, length, &mattr, ddma_flag, DDI_DMA_SLEEP,
            NULL, kvap, &real_length, acch) != DDI_SUCCESS) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "Failed to allocate DMA mem");
                ddi_dma_free_handle(dmah);
                *dmah = NULL;
                return (B_FALSE);
        }

        if (ddi_dma_addr_bind_handle(*dmah, NULL, *kvap, real_length,
            ddabh_flag, DDI_DMA_SLEEP, NULL, &cookie, &cookie_cnt)
            != DDI_DMA_MAPPED) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL, "Failed to bind DMA");
                ddi_dma_free_handle(dmah);
                ddi_dma_mem_free(acch);
                *dmah = NULL;
                *acch = NULL;
                return (B_FALSE);
        }

        if (cookie_cnt != 1) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL, "Multiple cookies");
                if (ddi_dma_unbind_handle(*dmah) != DDI_SUCCESS) {
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL, "Condition "
                            "failed at %s():%d", __func__, __LINE__);
                }
                ddi_dma_free_handle(dmah);
                ddi_dma_mem_free(acch);
                *dmah = NULL;
                *acch = NULL;
                return (B_FALSE);
        }

        *dma_addr = cookie.dmac_laddress;

        return (B_TRUE);
}

/*
 * Flush requested queues for a particular target.  Called with statlock held
 */
void
pmcs_flush_target_queues(pmcs_hw_t *pwp, pmcs_xscsi_t *tgt, uint8_t queues)
{
        pmcs_cmd_t      *sp, *sp_next;
        pmcwork_t       *pwrk;

        ASSERT(pwp != NULL);
        ASSERT(tgt != NULL);

        pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, tgt,
            "%s: Flushing queues (%d) for target 0x%p", __func__,
            queues, (void *)tgt);

        /*
         * Commands on the wait queue (or the special queue below) don't have
         * work structures associated with them.
         */
        if (queues & PMCS_TGT_WAIT_QUEUE) {
                mutex_enter(&tgt->wqlock);
                while ((sp = STAILQ_FIRST(&tgt->wq)) != NULL) {
                        STAILQ_REMOVE(&tgt->wq, sp, pmcs_cmd, cmd_next);
                        pmcs_prt(pwp, PMCS_PRT_DEBUG1, NULL, tgt,
                            "%s: Removing cmd 0x%p from wq for target 0x%p",
                            __func__, (void *)sp, (void *)tgt);
                        CMD2PKT(sp)->pkt_reason = CMD_DEV_GONE;
                        CMD2PKT(sp)->pkt_state = STATE_GOT_BUS;
                        mutex_exit(&tgt->wqlock);
                        pmcs_dma_unload(pwp, sp);
                        mutex_enter(&pwp->cq_lock);
                        STAILQ_INSERT_TAIL(&pwp->cq, sp, cmd_next);
                        PMCS_CQ_RUN_LOCKED(pwp);
                        mutex_exit(&pwp->cq_lock);
                        mutex_enter(&tgt->wqlock);
                }
                mutex_exit(&tgt->wqlock);
        }

        /*
         * Commands on the active queue will have work structures associated
         * with them.
         */
        if (queues & PMCS_TGT_ACTIVE_QUEUE) {
                mutex_exit(&tgt->statlock);
                mutex_enter(&tgt->aqlock);
                sp = STAILQ_FIRST(&tgt->aq);
                while (sp) {
                        sp_next = STAILQ_NEXT(sp, cmd_next);
                        pwrk = pmcs_tag2wp(pwp, sp->cmd_tag, B_FALSE);

                        /*
                         * If we don't find a work structure, it's because
                         * the command is already complete.  If so, move on
                         * to the next one.
                         */
                        if (pwrk == NULL) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG1, tgt->phy, tgt,
                                    "%s: Not removing cmd 0x%p (htag 0x%x) "
                                    "from aq", __func__, (void *)sp,
                                    sp->cmd_tag);
                                sp = sp_next;
                                continue;
                        }

                        STAILQ_REMOVE(&tgt->aq, sp, pmcs_cmd, cmd_next);
                        pmcs_prt(pwp, PMCS_PRT_DEBUG1, tgt->phy, tgt,
                            "%s: Removing cmd 0x%p (htag 0x%x) from aq for "
                            "target 0x%p", __func__, (void *)sp, sp->cmd_tag,
                            (void *)tgt);
                        mutex_exit(&tgt->aqlock);

                        /*
                         * Mark the work structure as dead and complete it
                         */
                        pwrk->dead = 1;
                        CMD2PKT(sp)->pkt_reason = CMD_DEV_GONE;
                        CMD2PKT(sp)->pkt_state = STATE_GOT_BUS;
                        pmcs_complete_work_impl(pwp, pwrk, NULL, 0);
                        pmcs_dma_unload(pwp, sp);
                        mutex_enter(&pwp->cq_lock);
                        STAILQ_INSERT_TAIL(&pwp->cq, sp, cmd_next);
                        mutex_exit(&pwp->cq_lock);
                        mutex_enter(&tgt->aqlock);
                        sp = sp_next;
                }
                mutex_exit(&tgt->aqlock);
                mutex_enter(&tgt->statlock);
        }

        if (queues & PMCS_TGT_SPECIAL_QUEUE) {
                while ((sp = STAILQ_FIRST(&tgt->sq)) != NULL) {
                        STAILQ_REMOVE(&tgt->sq, sp, pmcs_cmd, cmd_next);
                        pmcs_prt(pwp, PMCS_PRT_DEBUG1, tgt->phy, tgt,
                            "%s: Removing cmd 0x%p from sq for target 0x%p",
                            __func__, (void *)sp, (void *)tgt);
                        CMD2PKT(sp)->pkt_reason = CMD_DEV_GONE;
                        CMD2PKT(sp)->pkt_state = STATE_GOT_BUS;
                        pmcs_dma_unload(pwp, sp);
                        mutex_enter(&pwp->cq_lock);
                        STAILQ_INSERT_TAIL(&pwp->cq, sp, cmd_next);
                        mutex_exit(&pwp->cq_lock);
                }
        }

        if (queues == PMCS_TGT_ALL_QUEUES) {
                mutex_exit(&tgt->statlock);
                pmcs_flush_nonio_cmds(pwp, tgt);
                mutex_enter(&tgt->statlock);
        }
}

/*
 * Flush non-IO commands for this target. This cleans up the off-queue
 * work with no pmcs_cmd_t associated.
 */
static void
pmcs_flush_nonio_cmds(pmcs_hw_t *pwp, pmcs_xscsi_t *tgt)
{
        int i;
        pmcwork_t *p;

        for (i = 0; i < pwp->max_cmd; i++) {
                p = &pwp->work[i];
                mutex_enter(&p->lock);
                if (p->xp != tgt) {
                        mutex_exit(&p->lock);
                        continue;
                }
                if (p->htag & PMCS_TAG_NONIO_CMD) {
                        if (!PMCS_COMMAND_ACTIVE(p) || PMCS_COMMAND_DONE(p)) {
                                mutex_exit(&p->lock);
                                continue;
                        }
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, p->phy, p->xp,
                            "%s: Completing non-io cmd with HTAG 0x%x",
                            __func__, p->htag);
                        pmcs_complete_work_impl(pwp, p, NULL, 0);
                } else {
                        mutex_exit(&p->lock);
                }
        }
}

void
pmcs_complete_work_impl(pmcs_hw_t *pwp, pmcwork_t *pwrk, uint32_t *iomb,
    size_t amt)
{
        pmcs_phy_t      *pptr = NULL;

        switch (PMCS_TAG_TYPE(pwrk->htag)) {
        case PMCS_TAG_TYPE_CBACK:
        {
                pmcs_cb_t callback = (pmcs_cb_t)pwrk->ptr;
                (*callback)(pwp, pwrk, iomb);
                break;
        }
        case PMCS_TAG_TYPE_WAIT:
                if (pwrk->arg && iomb && amt) {
                        (void) memcpy(pwrk->arg, iomb, amt);
                }
                cv_signal(&pwrk->sleep_cv);
                mutex_exit(&pwrk->lock);
                break;
        case PMCS_TAG_TYPE_NONE:
#ifdef DEBUG
                pmcs_check_iomb_status(pwp, iomb);
#endif
                pptr = pwrk->phy;
                pmcs_pwork(pwp, pwrk);

                /* If this was an abort all, clean up if needed */
                if ((pwrk->abt_htag == PMCS_ABT_HTAG_ALL) && (pptr != NULL)) {
                        mutex_enter(&pptr->phy_lock);
                        if (pptr->abort_all_start) {
                                pptr->abort_all_start = 0;
                                cv_signal(&pptr->abort_all_cv);
                        }
                        mutex_exit(&pptr->phy_lock);
                }
                break;
        default:
                /*
                 * We will leak a structure here if we don't know
                 * what happened
                 */
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: Unknown PMCS_TAG_TYPE (%x)",
                    __func__, PMCS_TAG_TYPE(pwrk->htag));
                break;
        }
}

/*
 * Determine if iport still has targets. During detach(9E), if SCSA is
 * successfull in its guarantee of tran_tgt_free(9E) before detach(9E),
 * this should always return B_FALSE.
 */
boolean_t
pmcs_iport_has_targets(pmcs_hw_t *pwp, pmcs_iport_t *iport)
{
        pmcs_xscsi_t *xp;
        int i;

        mutex_enter(&pwp->lock);

        if (!pwp->targets || !pwp->max_dev) {
                mutex_exit(&pwp->lock);
                return (B_FALSE);
        }

        for (i = 0; i < pwp->max_dev; i++) {
                xp = pwp->targets[i];
                if ((xp == NULL) || (xp->phy == NULL) ||
                    (xp->phy->iport != iport)) {
                        continue;
                }

                mutex_exit(&pwp->lock);
                return (B_TRUE);
        }

        mutex_exit(&pwp->lock);
        return (B_FALSE);
}

/*
 * Called with softstate lock held
 */
void
pmcs_destroy_target(pmcs_xscsi_t *target)
{
        pmcs_hw_t *pwp = target->pwp;
        pmcs_iport_t *iport;

        ASSERT(pwp);
        ASSERT(mutex_owned(&pwp->lock));

        if (!target->ua) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, target,
                    "%s: target %p iport address is null",
                    __func__, (void *)target);
        }

        iport = pmcs_get_iport_by_ua(pwp, target->ua);
        if (iport == NULL) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, target,
                    "%s: no iport associated with tgt(0x%p)",
                    __func__, (void *)target);
                return;
        }

        pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, NULL, target,
            "%s: free target %p", __func__, (void *)target);
        if (target->ua) {
                strfree(target->ua);
        }

        mutex_destroy(&target->wqlock);
        mutex_destroy(&target->aqlock);
        mutex_destroy(&target->statlock);
        cv_destroy(&target->reset_cv);
        cv_destroy(&target->abort_cv);
        ddi_soft_state_bystr_fini(&target->lun_sstate);
        ddi_soft_state_bystr_free(iport->tgt_sstate, target->unit_address);
        pmcs_rele_iport(iport);
}

/*
 * pmcs_lock_phy_impl
 *
 * This function is what does the actual work for pmcs_lock_phy.  It will
 * lock all PHYs from phyp down in a top-down fashion.
 *
 * Locking notes:
 * 1. level starts from 0 for the PHY ("parent") that's passed in.  It is
 * not a reflection of the actual level of the PHY in the SAS topology.
 * 2. If parent is an expander, then parent is locked along with all its
 * descendents.
 * 3. Expander subsidiary PHYs at level 0 are not locked.  It is the
 * responsibility of the caller to individually lock expander subsidiary PHYs
 * at level 0 if necessary.
 * 4. Siblings at level 0 are not traversed due to the possibility that we're
 * locking a PHY on the dead list.  The siblings could be pointing to invalid
 * PHYs.  We don't lock siblings at level 0 anyway.
 */
static void
pmcs_lock_phy_impl(pmcs_phy_t *phyp, int level)
{
        pmcs_phy_t *tphyp;

        ASSERT((phyp->dtype == SAS) || (phyp->dtype == SATA) ||
            (phyp->dtype == EXPANDER) || (phyp->dtype == NOTHING));

        /*
         * Start walking the PHYs.
         */
        tphyp = phyp;
        while (tphyp) {
                /*
                 * If we're at the top level, only lock ourselves.  For anything
                 * at level > 0, traverse children while locking everything.
                 */
                if ((level > 0) || (tphyp == phyp)) {
                        pmcs_prt(tphyp->pwp, PMCS_PRT_DEBUG_PHY_LOCKING, tphyp,
                            NULL, "%s: PHY 0x%p parent 0x%p path %s lvl %d",
                            __func__, (void *)tphyp, (void *)tphyp->parent,
                            tphyp->path, level);
                        mutex_enter(&tphyp->phy_lock);

                        if (tphyp->children) {
                                pmcs_lock_phy_impl(tphyp->children, level + 1);
                        }
                }

                if (level == 0) {
                        return;
                }

                tphyp = tphyp->sibling;
        }
}

/*
 * pmcs_lock_phy
 *
 * This function is responsible for locking a PHY and all its descendents
 */
void
pmcs_lock_phy(pmcs_phy_t *phyp)
{
#ifdef DEBUG
        char *callername = NULL;
        ulong_t off;

        ASSERT(phyp != NULL);

        callername = modgetsymname((uintptr_t)caller(), &off);

        if (callername == NULL) {
                pmcs_prt(phyp->pwp, PMCS_PRT_DEBUG_PHY_LOCKING, phyp, NULL,
                    "%s: PHY 0x%p path %s caller: unknown", __func__,
                    (void *)phyp, phyp->path);
        } else {
                pmcs_prt(phyp->pwp, PMCS_PRT_DEBUG_PHY_LOCKING, phyp, NULL,
                    "%s: PHY 0x%p path %s caller: %s+%lx", __func__,
                    (void *)phyp, phyp->path, callername, off);
        }
#else
        pmcs_prt(phyp->pwp, PMCS_PRT_DEBUG_PHY_LOCKING, phyp, NULL,
            "%s: PHY 0x%p path %s", __func__, (void *)phyp, phyp->path);
#endif
        pmcs_lock_phy_impl(phyp, 0);
}

/*
 * pmcs_unlock_phy_impl
 *
 * Unlock all PHYs from phyp down in a bottom-up fashion.
 */
static void
pmcs_unlock_phy_impl(pmcs_phy_t *phyp, int level)
{
        pmcs_phy_t *phy_next;

        ASSERT((phyp->dtype == SAS) || (phyp->dtype == SATA) ||
            (phyp->dtype == EXPANDER) || (phyp->dtype == NOTHING));

        /*
         * Recurse down to the bottom PHYs
         */
        if (level == 0) {
                if (phyp->children) {
                        pmcs_unlock_phy_impl(phyp->children, level + 1);
                }
        } else {
                phy_next = phyp;
                while (phy_next) {
                        if (phy_next->children) {
                                pmcs_unlock_phy_impl(phy_next->children,
                                    level + 1);
                        }
                        phy_next = phy_next->sibling;
                }
        }

        /*
         * Iterate through PHYs unlocking all at level > 0 as well the top PHY
         */
        phy_next = phyp;
        while (phy_next) {
                if ((level > 0) || (phy_next == phyp)) {
                        pmcs_prt(phy_next->pwp, PMCS_PRT_DEBUG_PHY_LOCKING,
                            phy_next, NULL,
                            "%s: PHY 0x%p parent 0x%p path %s lvl %d",
                            __func__, (void *)phy_next,
                            (void *)phy_next->parent, phy_next->path, level);
                        mutex_exit(&phy_next->phy_lock);
                }

                if (level == 0) {
                        return;
                }

                phy_next = phy_next->sibling;
        }
}

/*
 * pmcs_unlock_phy
 *
 * Unlock a PHY and all its descendents
 */
void
pmcs_unlock_phy(pmcs_phy_t *phyp)
{
#ifdef DEBUG
        char *callername = NULL;
        ulong_t off;

        ASSERT(phyp != NULL);

        callername = modgetsymname((uintptr_t)caller(), &off);

        if (callername == NULL) {
                pmcs_prt(phyp->pwp, PMCS_PRT_DEBUG_PHY_LOCKING, phyp, NULL,
                    "%s: PHY 0x%p path %s caller: unknown", __func__,
                    (void *)phyp, phyp->path);
        } else {
                pmcs_prt(phyp->pwp, PMCS_PRT_DEBUG_PHY_LOCKING, phyp, NULL,
                    "%s: PHY 0x%p path %s caller: %s+%lx", __func__,
                    (void *)phyp, phyp->path, callername, off);
        }
#else
        pmcs_prt(phyp->pwp, PMCS_PRT_DEBUG_PHY_LOCKING, phyp, NULL,
            "%s: PHY 0x%p path %s", __func__, (void *)phyp, phyp->path);
#endif
        pmcs_unlock_phy_impl(phyp, 0);
}

/*
 * pmcs_get_root_phy
 *
 * For a given phy pointer return its root phy.
 * This function must only be called during discovery in order to ensure that
 * the chain of PHYs from phyp up to the root PHY doesn't change.
 */
pmcs_phy_t *
pmcs_get_root_phy(pmcs_phy_t *phyp)
{
        ASSERT(phyp);

        while (phyp) {
                if (IS_ROOT_PHY(phyp)) {
                        break;
                }
                phyp = phyp->parent;
        }

        return (phyp);
}

/*
 * pmcs_free_dma_chunklist
 *
 * Free DMA S/G chunk list
 */
void
pmcs_free_dma_chunklist(pmcs_hw_t *pwp)
{
        pmcs_chunk_t    *pchunk;

        while (pwp->dma_chunklist) {
                pchunk = pwp->dma_chunklist;
                pwp->dma_chunklist = pwp->dma_chunklist->next;
                if (pchunk->dma_handle) {
                        if (ddi_dma_unbind_handle(pchunk->dma_handle) !=
                            DDI_SUCCESS) {
                                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                                    "Condition failed at %s():%d",
                                    __func__, __LINE__);
                        }
                        ddi_dma_free_handle(&pchunk->dma_handle);
                        ddi_dma_mem_free(&pchunk->acc_handle);
                }
                kmem_free(pchunk, sizeof (pmcs_chunk_t));
        }
}

/*ARGSUSED2*/
int
pmcs_phy_constructor(void *buf, void *arg, int kmflags)
{
        pmcs_hw_t *pwp = (pmcs_hw_t *)arg;
        pmcs_phy_t *phyp = (pmcs_phy_t *)buf;

        mutex_init(&phyp->phy_lock, NULL, MUTEX_DRIVER,
            DDI_INTR_PRI(pwp->intr_pri));
        cv_init(&phyp->abort_all_cv, NULL, CV_DRIVER, NULL);
        return (0);
}

/*ARGSUSED1*/
void
pmcs_phy_destructor(void *buf, void *arg)
{
        pmcs_phy_t *phyp = (pmcs_phy_t *)buf;

        cv_destroy(&phyp->abort_all_cv);
        mutex_destroy(&phyp->phy_lock);
}

/*
 * Free all PHYs from the kmem_cache starting at phyp as well as everything
 * on the dead_phys list.
 *
 * NOTE: This function does not free root PHYs as they are not allocated
 * from the kmem_cache.
 *
 * No PHY locks are acquired as this should only be called during DDI_DETACH
 * or soft reset (while pmcs interrupts are disabled).
 */
void
pmcs_free_all_phys(pmcs_hw_t *pwp, pmcs_phy_t *phyp)
{
        pmcs_phy_t *tphyp, *nphyp, *cphyp;

        if (phyp == NULL) {
                return;
        }

        for (tphyp = phyp; tphyp; tphyp = nphyp) {
                nphyp = tphyp->sibling;
                cphyp = tphyp->children;

                if (cphyp) {
                        tphyp->children = NULL;
                        pmcs_free_all_phys(pwp, cphyp);
                }

                if (!IS_ROOT_PHY(tphyp)) {
                        tphyp->target_addr = NULL;
                        kmem_cache_free(pwp->phy_cache, tphyp);
                }
        }

        mutex_enter(&pwp->dead_phylist_lock);
        for (tphyp = pwp->dead_phys; tphyp; tphyp = nphyp) {
                nphyp = tphyp->dead_next;
                tphyp->target_addr = NULL;
                kmem_cache_free(pwp->phy_cache, tphyp);
        }
        pwp->dead_phys = NULL;
        mutex_exit(&pwp->dead_phylist_lock);
}

/*
 * Free a list of PHYs linked together by the sibling pointer back to the
 * kmem cache from whence they came.  This function does not recurse, so the
 * caller must ensure there are no children.
 */
void
pmcs_free_phys(pmcs_hw_t *pwp, pmcs_phy_t *phyp)
{
        pmcs_phy_t *next_phy;

        while (phyp) {
                next_phy = phyp->sibling;
                ASSERT(!mutex_owned(&phyp->phy_lock));
                phyp->target_addr = NULL;
                kmem_cache_free(pwp->phy_cache, phyp);
                phyp = next_phy;
        }
}

/*
 * Make a copy of an existing PHY structure.  This is used primarily in
 * discovery to compare the contents of an existing PHY with what gets
 * reported back by an expander.
 *
 * This function must not be called from any context where sleeping is
 * not possible.
 *
 * The new PHY is returned unlocked.
 */
static pmcs_phy_t *
pmcs_clone_phy(pmcs_phy_t *orig_phy)
{
        pmcs_phy_t *local;

        local = kmem_cache_alloc(orig_phy->pwp->phy_cache, KM_SLEEP);

        /*
         * Go ahead and just copy everything...
         */
        *local = *orig_phy;
        local->target_addr = &orig_phy->target;

        /*
         * But the following must be set appropriately for this copy
         */
        local->sibling = NULL;
        local->children = NULL;
        local->target = NULL;
        mutex_init(&local->phy_lock, NULL, MUTEX_DRIVER,
            DDI_INTR_PRI(orig_phy->pwp->intr_pri));

        return (local);
}

int
pmcs_check_acc_handle(ddi_acc_handle_t handle)
{
        ddi_fm_error_t de;

        if (handle == NULL) {
                return (DDI_FAILURE);
        }
        ddi_fm_acc_err_get(handle, &de, DDI_FME_VER0);
        return (de.fme_status);
}

int
pmcs_check_dma_handle(ddi_dma_handle_t handle)
{
        ddi_fm_error_t de;

        if (handle == NULL) {
                return (DDI_FAILURE);
        }
        ddi_fm_dma_err_get(handle, &de, DDI_FME_VER0);
        return (de.fme_status);
}


void
pmcs_fm_ereport(pmcs_hw_t *pwp, char *detail)
{
        uint64_t ena;
        char buf[FM_MAX_CLASS];

        (void) snprintf(buf, FM_MAX_CLASS, "%s.%s", DDI_FM_DEVICE, detail);
        ena = fm_ena_generate(0, FM_ENA_FMT1);
        if (DDI_FM_EREPORT_CAP(pwp->fm_capabilities)) {
                ddi_fm_ereport_post(pwp->dip, buf, ena, DDI_NOSLEEP,
                    FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERS0, NULL);
        }
}

int
pmcs_check_acc_dma_handle(pmcs_hw_t *pwp)
{
        pmcs_chunk_t *pchunk;
        int i;

        /* check all acc & dma handles allocated in attach */
        if ((pmcs_check_acc_handle(pwp->pci_acc_handle) != DDI_SUCCESS) ||
            (pmcs_check_acc_handle(pwp->msg_acc_handle) != DDI_SUCCESS) ||
            (pmcs_check_acc_handle(pwp->top_acc_handle) != DDI_SUCCESS) ||
            (pmcs_check_acc_handle(pwp->mpi_acc_handle) != DDI_SUCCESS) ||
            (pmcs_check_acc_handle(pwp->gsm_acc_handle) != DDI_SUCCESS)) {
                goto check_failed;
        }

        for (i = 0; i < PMCS_NIQ; i++) {
                if ((pmcs_check_dma_handle(
                    pwp->iqp_handles[i]) != DDI_SUCCESS) ||
                    (pmcs_check_acc_handle(
                    pwp->iqp_acchdls[i]) != DDI_SUCCESS)) {
                        goto check_failed;
                }
        }

        for (i = 0; i < PMCS_NOQ; i++) {
                if ((pmcs_check_dma_handle(
                    pwp->oqp_handles[i]) != DDI_SUCCESS) ||
                    (pmcs_check_acc_handle(
                    pwp->oqp_acchdls[i]) != DDI_SUCCESS)) {
                        goto check_failed;
                }
        }

        if ((pmcs_check_dma_handle(pwp->cip_handles) != DDI_SUCCESS) ||
            (pmcs_check_acc_handle(pwp->cip_acchdls) != DDI_SUCCESS)) {
                goto check_failed;
        }

        if (pwp->fwlog &&
            ((pmcs_check_dma_handle(pwp->fwlog_hndl) != DDI_SUCCESS) ||
            (pmcs_check_acc_handle(pwp->fwlog_acchdl) != DDI_SUCCESS))) {
                goto check_failed;
        }

        if (pwp->regdump_hndl && pwp->regdump_acchdl &&
            ((pmcs_check_dma_handle(pwp->regdump_hndl) != DDI_SUCCESS) ||
            (pmcs_check_acc_handle(pwp->regdump_acchdl)
            != DDI_SUCCESS))) {
                goto check_failed;
        }


        pchunk = pwp->dma_chunklist;
        while (pchunk) {
                if ((pmcs_check_acc_handle(pchunk->acc_handle)
                    != DDI_SUCCESS) ||
                    (pmcs_check_dma_handle(pchunk->dma_handle)
                    != DDI_SUCCESS)) {
                        goto check_failed;
                }
                pchunk = pchunk->next;
        }

        return (0);

check_failed:

        return (1);
}

/*
 * pmcs_handle_dead_phys
 *
 * If the PHY has no outstanding work associated with it, remove it from
 * the dead PHY list and free it.
 *
 * If pwp->ds_err_recovering or pwp->configuring is set, don't run.
 * This keeps routines that need to submit work to the chip from having to
 * hold PHY locks to ensure that PHYs don't disappear while they do their work.
 */
void
pmcs_handle_dead_phys(pmcs_hw_t *pwp)
{
        pmcs_phy_t *phyp, *nphyp, *pphyp;

        mutex_enter(&pwp->lock);
        mutex_enter(&pwp->config_lock);

        if (pwp->configuring | pwp->ds_err_recovering) {
                mutex_exit(&pwp->config_lock);
                mutex_exit(&pwp->lock);
                return;
        }

        /*
         * Check every PHY in the dead PHY list
         */
        mutex_enter(&pwp->dead_phylist_lock);
        phyp = pwp->dead_phys;
        pphyp = NULL;   /* Set previous PHY to NULL */

        while (phyp != NULL) {
                pmcs_lock_phy(phyp);
                ASSERT(phyp->dead);

                nphyp = phyp->dead_next;

                /*
                 * Check for outstanding work
                 */
                if (phyp->ref_count > 0) {
                        pmcs_unlock_phy(phyp);
                        pphyp = phyp;   /* This PHY becomes "previous" */
                } else if (phyp->target) {
                        pmcs_unlock_phy(phyp);
                        pmcs_prt(pwp, PMCS_PRT_DEBUG1, phyp, phyp->target,
                            "%s: Not freeing PHY 0x%p: target 0x%p is not free",
                            __func__, (void *)phyp, (void *)phyp->target);
                        pphyp = phyp;
                } else {
                        /*
                         * No outstanding work or target references. Remove it
                         * from the list and free it
                         */
                        pmcs_prt(pwp, PMCS_PRT_DEBUG, phyp, phyp->target,
                            "%s: Freeing inactive dead PHY 0x%p @ %s "
                            "target = 0x%p", __func__, (void *)phyp,
                            phyp->path, (void *)phyp->target);
                        /*
                         * If pphyp is NULL, then phyp was the head of the list,
                         * so just reset the head to nphyp. Otherwise, the
                         * previous PHY will now point to nphyp (the next PHY)
                         */
                        if (pphyp == NULL) {
                                pwp->dead_phys = nphyp;
                        } else {
                                pphyp->dead_next = nphyp;
                        }
                        /*
                         * If the target still points to this PHY, remove
                         * that linkage now.
                         */
                        if (phyp->target) {
                                mutex_enter(&phyp->target->statlock);
                                if (phyp->target->phy == phyp) {
                                        phyp->target->phy = NULL;
                                }
                                mutex_exit(&phyp->target->statlock);
                        }
                        pmcs_unlock_phy(phyp);
                        phyp->target_addr = NULL;
                        kmem_cache_free(pwp->phy_cache, phyp);
                }

                phyp = nphyp;
        }

        mutex_exit(&pwp->dead_phylist_lock);
        mutex_exit(&pwp->config_lock);
        mutex_exit(&pwp->lock);
}

void
pmcs_inc_phy_ref_count(pmcs_phy_t *phyp)
{
        atomic_inc_32(&phyp->ref_count);
}

void
pmcs_dec_phy_ref_count(pmcs_phy_t *phyp)
{
        ASSERT(phyp->ref_count != 0);
        atomic_dec_32(&phyp->ref_count);
}

/*
 * pmcs_reap_dead_phy
 *
 * This function is called from pmcs_new_tport when we have a PHY
 * without a target pointer.  It's possible in that case that this PHY
 * may have a "brother" on the dead_phys list.  That is, it may be the same as
 * this one but with a different root PHY number (e.g. pp05 vs. pp04).  If
 * that's the case, update the dead PHY and this new PHY.  If that's not the
 * case, we should get a tran_tgt_init on this after it's reported to SCSA.
 *
 * Called with PHY locked.
 */
static void
pmcs_reap_dead_phy(pmcs_phy_t *phyp)
{
        pmcs_hw_t *pwp = phyp->pwp;
        pmcs_phy_t *ctmp;
        pmcs_iport_t *iport_cmp;

        ASSERT(mutex_owned(&phyp->phy_lock));

        /*
         * Check the dead PHYs list
         */
        mutex_enter(&pwp->dead_phylist_lock);
        ctmp = pwp->dead_phys;
        while (ctmp) {
                /*
                 * If the iport is NULL, compare against last_iport.
                 */
                if (ctmp->iport) {
                        iport_cmp = ctmp->iport;
                } else {
                        iport_cmp = ctmp->last_iport;
                }

                if ((iport_cmp != phyp->iport) ||
                    (memcmp((void *)&ctmp->sas_address[0],
                    (void *)&phyp->sas_address[0], 8))) {
                        ctmp = ctmp->dead_next;
                        continue;
                }

                /*
                 * Same SAS address on same iport.  Now check to see if
                 * the PHY path is the same with the possible exception
                 * of the root PHY number.
                 * The "5" is the string length of "pp00."
                 */
                if ((strnlen(phyp->path, 5) >= 5) &&
                    (strnlen(ctmp->path, 5) >= 5)) {
                        if (memcmp((void *)&phyp->path[5],
                            (void *)&ctmp->path[5],
                            strnlen(phyp->path, 32) - 5) == 0) {
                                break;
                        }
                }

                ctmp = ctmp->dead_next;
        }
        mutex_exit(&pwp->dead_phylist_lock);

        /*
         * Found a match.  Remove the target linkage and drop the
         * ref count on the old PHY.  Then, increment the ref count
         * on the new PHY to compensate.
         */
        if (ctmp) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, ctmp, NULL,
                    "%s: Found match in dead PHY list (0x%p) for new PHY %s",
                    __func__, (void *)ctmp, phyp->path);
                /*
                 * If there is a pointer to the target in the dead PHY, move
                 * all reference counts to the new PHY.
                 */
                if (ctmp->target) {
                        mutex_enter(&ctmp->target->statlock);
                        phyp->target = ctmp->target;

                        while (ctmp->ref_count != 0) {
                                pmcs_inc_phy_ref_count(phyp);
                                pmcs_dec_phy_ref_count(ctmp);
                        }
                        /*
                         * Update the target's linkage as well
                         */
                        phyp->target->phy = phyp;
                        phyp->target->dtype = phyp->dtype;
                        ctmp->target = NULL;
                        mutex_exit(&phyp->target->statlock);
                }
        }
}

/*
 * Called with iport lock held
 */
void
pmcs_add_phy_to_iport(pmcs_iport_t *iport, pmcs_phy_t *phyp)
{
        ASSERT(mutex_owned(&iport->lock));
        ASSERT(phyp);
        ASSERT(!list_link_active(&phyp->list_node));

        iport->nphy++;
        list_insert_tail(&iport->phys, phyp);
        pmcs_smhba_add_iport_prop(iport, DATA_TYPE_INT32, PMCS_NUM_PHYS,
            &iport->nphy);
        mutex_enter(&phyp->phy_lock);
        pmcs_create_one_phy_stats(iport, phyp);
        mutex_exit(&phyp->phy_lock);
        pmcs_hold_iport(iport);
}

/*
 * Called with the iport lock held
 */
void
pmcs_remove_phy_from_iport(pmcs_iport_t *iport, pmcs_phy_t *phyp)
{
        pmcs_phy_t *pptr, *next_pptr;

        ASSERT(mutex_owned(&iport->lock));

        /*
         * If phyp is NULL, remove all PHYs from the iport
         */
        if (phyp == NULL) {
                for (pptr = list_head(&iport->phys); pptr != NULL;
                    pptr = next_pptr) {
                        next_pptr = list_next(&iport->phys, pptr);
                        mutex_enter(&pptr->phy_lock);
                        if (pptr->phy_stats != NULL) {
                                kstat_delete(pptr->phy_stats);
                                pptr->phy_stats = NULL;
                        }
                        pptr->iport = NULL;
                        pmcs_update_phy_pm_props(pptr, pptr->att_port_pm_tmp,
                            pptr->tgt_port_pm_tmp, B_FALSE);
                        mutex_exit(&pptr->phy_lock);
                        pmcs_rele_iport(iport);
                        list_remove(&iport->phys, pptr);
                        pmcs_smhba_add_iport_prop(iport, DATA_TYPE_INT32,
                            PMCS_NUM_PHYS, &iport->nphy);
                }
                iport->nphy = 0;
                return;
        }

        ASSERT(phyp);
        ASSERT(iport->nphy > 0);
        ASSERT(list_link_active(&phyp->list_node));
        iport->nphy--;
        list_remove(&iport->phys, phyp);
        pmcs_update_phy_pm_props(phyp, phyp->att_port_pm_tmp,
            phyp->tgt_port_pm_tmp, B_FALSE);
        pmcs_smhba_add_iport_prop(iport, DATA_TYPE_INT32, PMCS_NUM_PHYS,
            &iport->nphy);
        pmcs_rele_iport(iport);
}

/*
 * This function checks to see if the target pointed to by phyp is still
 * correct.  This is done by comparing the target's unit address with the
 * SAS address in phyp.
 *
 * Called with PHY locked and target statlock held
 */
static boolean_t
pmcs_phy_target_match(pmcs_phy_t *phyp)
{
        uint64_t wwn;
        char unit_address[PMCS_MAX_UA_SIZE];
        boolean_t rval = B_FALSE;

        ASSERT(phyp);
        ASSERT(phyp->target);
        ASSERT(mutex_owned(&phyp->phy_lock));
        ASSERT(mutex_owned(&phyp->target->statlock));

        wwn = pmcs_barray2wwn(phyp->sas_address);
        (void) scsi_wwn_to_wwnstr(wwn, 1, unit_address);

        if (memcmp((void *)unit_address, (void *)phyp->target->unit_address,
            strnlen(phyp->target->unit_address, PMCS_MAX_UA_SIZE)) == 0) {
                rval = B_TRUE;
        }

        return (rval);
}
/*
 * Commands used to serialize SMP requests.
 *
 * The SPC only allows 2 SMP commands per SMP target: 1 cmd pending and 1 cmd
 * queued for the same SMP target. If a third SMP cmd is sent to the SPC for an
 * SMP target that already has a SMP cmd pending and one queued, then the
 * SPC responds with the ERROR_INTERNAL_SMP_RESOURCE response.
 *
 * Additionally, the SPC has an 8 entry deep cmd queue and the number of SMP
 * cmds that can be queued is controlled by the PORT_CONTROL IOMB. The
 * SPC default is 1 SMP command/port (iport).  These 2 queued SMP cmds would
 * have to be for different SMP targets.  The INTERNAL_SMP_RESOURCE error will
 * also be returned if a 2nd SMP cmd is sent to the controller when there is
 * already 1 SMP cmd queued for that port or if a 3rd SMP cmd is sent to the
 * queue if there are already 2 queued SMP cmds.
 */
void
pmcs_smp_acquire(pmcs_iport_t *iport)
{
        if (iport == NULL) {
                return;
        }

        mutex_enter(&iport->smp_lock);
        while (iport->smp_active) {
                pmcs_prt(iport->pwp, PMCS_PRT_DEBUG_IPORT, NULL, NULL,
                    "%s: SMP is active on thread 0x%p, waiting", __func__,
                    (void *)iport->smp_active_thread);
                cv_wait(&iport->smp_cv, &iport->smp_lock);
        }
        iport->smp_active = B_TRUE;
        iport->smp_active_thread = curthread;
        pmcs_prt(iport->pwp, PMCS_PRT_DEBUG3, NULL, NULL,
            "%s: SMP acquired by thread 0x%p", __func__,
            (void *)iport->smp_active_thread);
        mutex_exit(&iport->smp_lock);
}

void
pmcs_smp_release(pmcs_iport_t *iport)
{
        if (iport == NULL) {
                return;
        }

        mutex_enter(&iport->smp_lock);
        pmcs_prt(iport->pwp, PMCS_PRT_DEBUG3, NULL, NULL,
            "%s: SMP released by thread 0x%p", __func__, (void *)curthread);
        iport->smp_active = B_FALSE;
        iport->smp_active_thread = NULL;
        cv_signal(&iport->smp_cv);
        mutex_exit(&iport->smp_lock);
}

/*
 * Update a PHY's attached-port-pm and target-port-pm properties
 *
 * phyp: PHY whose properties are to be updated
 *
 * att_bv: Bit value of the attached-port-pm property to be updated in the
 * 64-bit holding area for the PHY.
 *
 * tgt_bv: Bit value of the target-port-pm property to update in the 64-bit
 * holding area for the PHY.
 *
 * prop_add_val: If TRUE, we're adding bits into the property value.
 * Otherwise, we're taking them out.  Either way, the properties for this
 * PHY will be updated.
 */
void
pmcs_update_phy_pm_props(pmcs_phy_t *phyp, uint64_t att_bv, uint64_t tgt_bv,
    boolean_t prop_add_val)
{
        pmcs_xscsi_t    *tgt;

        if (prop_add_val) {
                /*
                 * If the values are currently 0, then we're setting the
                 * phymask for just this PHY as well.
                 */
                if (phyp->att_port_pm_tmp == 0) {
                        phyp->att_port_pm = att_bv;
                        phyp->tgt_port_pm = tgt_bv;
                }
                phyp->att_port_pm_tmp |= att_bv;
                phyp->tgt_port_pm_tmp |= tgt_bv;
                (void) snprintf(phyp->att_port_pm_str, PMCS_PM_MAX_NAMELEN,
                    "%"PRIx64, phyp->att_port_pm_tmp);
                (void) snprintf(phyp->tgt_port_pm_str, PMCS_PM_MAX_NAMELEN,
                    "%"PRIx64, phyp->tgt_port_pm_tmp);
        } else {
                phyp->att_port_pm_tmp &= ~att_bv;
                phyp->tgt_port_pm_tmp &= ~tgt_bv;
                if (phyp->att_port_pm_tmp) {
                        (void) snprintf(phyp->att_port_pm_str,
                            PMCS_PM_MAX_NAMELEN, "%"PRIx64,
                            phyp->att_port_pm_tmp);
                } else {
                        phyp->att_port_pm_str[0] = '\0';
                        phyp->att_port_pm = 0;
                }
                if (phyp->tgt_port_pm_tmp) {
                        (void) snprintf(phyp->tgt_port_pm_str,
                            PMCS_PM_MAX_NAMELEN, "%"PRIx64,
                            phyp->tgt_port_pm_tmp);
                } else {
                        phyp->tgt_port_pm_str[0] = '\0';
                        phyp->tgt_port_pm = 0;
                }
        }

        if ((phyp->target_addr) && (*phyp->target_addr != NULL)) {
                tgt = *phyp->target_addr;
        } else if (phyp->target != NULL) {
                tgt = phyp->target;
        } else {
                return;
        }

        mutex_enter(&tgt->statlock);
        if (!list_is_empty(&tgt->lun_list)) {
                pmcs_lun_t *lunp;

                lunp = list_head(&tgt->lun_list);
                while (lunp) {
                        (void) scsi_device_prop_update_string(lunp->sd,
                            SCSI_DEVICE_PROP_PATH,
                            SCSI_ADDR_PROP_ATTACHED_PORT_PM,
                            phyp->att_port_pm_str);
                        (void) scsi_device_prop_update_string(lunp->sd,
                            SCSI_DEVICE_PROP_PATH,
                            SCSI_ADDR_PROP_TARGET_PORT_PM,
                            phyp->tgt_port_pm_str);
                        lunp = list_next(&tgt->lun_list, lunp);
                }
        } else if (tgt->smpd) {
                (void) smp_device_prop_update_string(tgt->smpd,
                    SCSI_ADDR_PROP_ATTACHED_PORT_PM,
                    phyp->att_port_pm_str);
                (void) smp_device_prop_update_string(tgt->smpd,
                    SCSI_ADDR_PROP_TARGET_PORT_PM,
                    phyp->tgt_port_pm_str);
        }
        mutex_exit(&tgt->statlock);
}

/* ARGSUSED */
void
pmcs_deregister_device_work(pmcs_hw_t *pwp, pmcs_phy_t *phyp)
{
        pmcs_phy_t      *pptr;

        for (pptr = pwp->root_phys; pptr; pptr = pptr->sibling) {
                pmcs_lock_phy(pptr);
                if (pptr->deregister_wait) {
                        pmcs_deregister_device(pwp, pptr);
                }
                pmcs_unlock_phy(pptr);
        }
}

/*
 * pmcs_iport_active
 *
 * Mark this iport as active.  Called with the iport lock held.
 */
static void
pmcs_iport_active(pmcs_iport_t *iport)
{
        ASSERT(mutex_owned(&iport->lock));

        iport->ua_state = UA_ACTIVE;
        iport->smp_active = B_FALSE;
        iport->smp_active_thread = NULL;
}

/* ARGSUSED */
static void
pmcs_tgtmap_activate_cb(void *tgtmap_priv, char *tgt_addr,
    scsi_tgtmap_tgt_type_t tgt_type, void **tgt_privp)
{
        pmcs_iport_t *iport = (pmcs_iport_t *)tgtmap_priv;
        pmcs_hw_t *pwp = iport->pwp;
        pmcs_xscsi_t *target;

        /*
         * Look up the target.  If there is one, and it doesn't have a PHY
         * pointer, re-establish that linkage here.
         */
        mutex_enter(&pwp->lock);
        target = pmcs_get_target(iport, tgt_addr, B_FALSE);
        mutex_exit(&pwp->lock);

        /*
         * If we got a target, it will now have a PHY pointer and the PHY
         * will point to the target.  The PHY will be locked, so we'll need
         * to unlock it.
         */
        if (target != NULL) {
                pmcs_unlock_phy(target->phy);
        }

        /*
         * Update config_restart_time so we don't try to restart discovery
         * while enumeration is still in progress.
         */
        mutex_enter(&pwp->config_lock);
        pwp->config_restart_time = ddi_get_lbolt() +
            drv_usectohz(PMCS_REDISCOVERY_DELAY);
        mutex_exit(&pwp->config_lock);
}

/* ARGSUSED */
static boolean_t
pmcs_tgtmap_deactivate_cb(void *tgtmap_priv, char *tgt_addr,
    scsi_tgtmap_tgt_type_t tgt_type, void *tgt_priv,
    scsi_tgtmap_deact_rsn_t tgt_deact_rsn)
{
        pmcs_iport_t *iport = (pmcs_iport_t *)tgtmap_priv;
        pmcs_phy_t *phyp;
        boolean_t rediscover = B_FALSE;

        ASSERT(iport);

        phyp = pmcs_find_phy_by_sas_address(iport->pwp, iport, NULL, tgt_addr);
        if (phyp == NULL) {
                pmcs_prt(iport->pwp, PMCS_PRT_DEBUG_IPORT, NULL, NULL,
                    "%s: Couldn't find PHY at %s", __func__, tgt_addr);
                return (rediscover);
        }
        /* phyp is locked */

        if (!phyp->reenumerate && phyp->configured) {
                pmcs_prt(iport->pwp, PMCS_PRT_DEBUG_CONFIG, phyp, phyp->target,
                    "%s: PHY @ %s is configured... re-enumerate", __func__,
                    tgt_addr);
                phyp->reenumerate = 1;
        }

        /*
         * Check to see if reenumerate is set, and if so, if we've reached our
         * maximum number of retries.
         */
        if (phyp->reenumerate) {
                if (phyp->enum_attempts == PMCS_MAX_REENUMERATE) {
                        pmcs_prt(iport->pwp, PMCS_PRT_DEBUG_CONFIG, phyp,
                            phyp->target,
                            "%s: No more enumeration attempts for %s", __func__,
                            tgt_addr);
                } else {
                        pmcs_prt(iport->pwp, PMCS_PRT_DEBUG_CONFIG, phyp,
                            phyp->target, "%s: Re-attempt enumeration for %s",
                            __func__, tgt_addr);
                        ++phyp->enum_attempts;
                        rediscover = B_TRUE;
                }

                phyp->reenumerate = 0;
        }

        pmcs_unlock_phy(phyp);

        mutex_enter(&iport->pwp->config_lock);
        iport->pwp->config_restart_time = ddi_get_lbolt() +
            drv_usectohz(PMCS_REDISCOVERY_DELAY);
        if (rediscover) {
                iport->pwp->config_restart = B_TRUE;
        } else if (iport->pwp->config_restart == B_TRUE) {
                /*
                 * If we aren't asking for rediscovery because of this PHY,
                 * check to see if we're already asking for it on behalf of
                 * some other PHY.  If so, we'll want to return TRUE, so reset
                 * "rediscover" here.
                 */
                rediscover = B_TRUE;
        }

        mutex_exit(&iport->pwp->config_lock);

        return (rediscover);
}

void
pmcs_status_disposition(pmcs_phy_t *phyp, uint32_t status)
{
        ASSERT(phyp);
        ASSERT(!mutex_owned(&phyp->phy_lock));

        if (phyp == NULL) {
                return;
        }

        pmcs_lock_phy(phyp);

        /*
         * XXX: Do we need to call this function from an SSP_EVENT?
         */

        switch (status) {
        case PMCOUT_STATUS_NO_DEVICE:
        case PMCOUT_STATUS_ERROR_HW_TIMEOUT:
        case PMCOUT_STATUS_XFER_ERR_BREAK:
        case PMCOUT_STATUS_XFER_ERR_PHY_NOT_READY:
        case PMCOUT_STATUS_OPEN_CNX_PROTOCOL_NOT_SUPPORTED:
        case PMCOUT_STATUS_OPEN_CNX_ERROR_ZONE_VIOLATION:
        case PMCOUT_STATUS_OPEN_CNX_ERROR_BREAK:
        case PMCOUT_STATUS_OPENCNX_ERROR_BAD_DESTINATION:
        case PMCOUT_STATUS_OPEN_CNX_ERROR_CONNECTION_RATE_NOT_SUPPORTED:
        case PMCOUT_STATUS_OPEN_CNX_ERROR_STP_RESOURCES_BUSY:
        case PMCOUT_STATUS_OPEN_CNX_ERROR_WRONG_DESTINATION:
        case PMCOUT_STATUS_OPEN_CNX_ERROR_UNKNOWN_ERROR:
        case PMCOUT_STATUS_IO_XFER_ERROR_NAK_RECEIVED:
        case PMCOUT_STATUS_XFER_ERROR_RX_FRAME:
        case PMCOUT_STATUS_IO_XFER_OPEN_RETRY_TIMEOUT:
        case PMCOUT_STATUS_ERROR_INTERNAL_SMP_RESOURCE:
        case PMCOUT_STATUS_IO_PORT_IN_RESET:
        case PMCOUT_STATUS_IO_DS_NON_OPERATIONAL:
        case PMCOUT_STATUS_IO_DS_IN_RECOVERY:
        case PMCOUT_STATUS_IO_OPEN_CNX_ERROR_HW_RESOURCE_BUSY:
                pmcs_prt(phyp->pwp, PMCS_PRT_DEBUG, phyp, phyp->target,
                    "%s: status = 0x%x for " SAS_ADDR_FMT ", reenumerate",
                    __func__, status, SAS_ADDR_PRT(phyp->sas_address));
                phyp->reenumerate = 1;
                break;

        default:
                pmcs_prt(phyp->pwp, PMCS_PRT_DEBUG, phyp, phyp->target,
                    "%s: status = 0x%x for " SAS_ADDR_FMT ", no reenumeration",
                    __func__, status, SAS_ADDR_PRT(phyp->sas_address));
                break;
        }

        pmcs_unlock_phy(phyp);
}

/*
 * Add the list of PHYs pointed to by phyp to the dead_phys_list
 *
 * Called with all PHYs in the list locked
 */
static void
pmcs_add_dead_phys(pmcs_hw_t *pwp, pmcs_phy_t *phyp)
{
        mutex_enter(&pwp->dead_phylist_lock);
        while (phyp) {
                pmcs_phy_t *nxt = phyp->sibling;
                ASSERT(phyp->dead);
                pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, phyp, NULL,
                    "%s: dead PHY 0x%p (%s) (ref_count %d)", __func__,
                    (void *)phyp, phyp->path, phyp->ref_count);
                /*
                 * Put this PHY on the dead PHY list for the watchdog to
                 * clean up after any outstanding work has completed.
                 */
                phyp->dead_next = pwp->dead_phys;
                pwp->dead_phys = phyp;
                pmcs_unlock_phy(phyp);
                phyp = nxt;
        }
        mutex_exit(&pwp->dead_phylist_lock);
}

static void
pmcs_get_fw_version(pmcs_hw_t *pwp)
{
        uint32_t ila_len, ver_hi, ver_lo;
        uint8_t ila_ver_string[9], img_flag;
        char uc, *ucp = &uc;
        unsigned long ila_ver;
        uint64_t ver_hilo;

        /* Firmware version is easy. */
        pwp->fw = pmcs_rd_mpi_tbl(pwp, PMCS_MPI_FW);

        /*
         * Get the image size (2nd to last dword)
         * NOTE: The GSM registers are mapped little-endian, but the data
         * on the flash is actually big-endian, so we need to swap these values
         * regardless of which platform we're on.
         */
        ila_len = BSWAP_32(pmcs_rd_gsm_reg(pwp, GSM_FLASH_BASE_UPPER,
            GSM_FLASH_BASE + GSM_SM_BLKSZ - (2 << 2)));
        if (ila_len > 65535) {
                pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, NULL,
                    "%s: Invalid ILA image size (0x%x)?", __func__, ila_len);
                return;
        }

        /*
         * The numeric version is at ila_len - PMCS_ILA_VER_OFFSET
         */
        ver_hi = BSWAP_32(pmcs_rd_gsm_reg(pwp, GSM_FLASH_BASE_UPPER,
            GSM_FLASH_BASE + ila_len - PMCS_ILA_VER_OFFSET));
        ver_lo = BSWAP_32(pmcs_rd_gsm_reg(pwp, GSM_FLASH_BASE_UPPER,
            GSM_FLASH_BASE + ila_len - PMCS_ILA_VER_OFFSET + 4));
        ver_hilo = BE_64(((uint64_t)ver_hi << 32) | ver_lo);
        bcopy((const void *)&ver_hilo, &ila_ver_string[0], 8);
        ila_ver_string[8] = '\0';

        (void) ddi_strtoul((const char *)ila_ver_string, &ucp, 16, &ila_ver);
        pwp->ila_ver = (int)(ila_ver & 0xffffffff);

        img_flag = (BSWAP_32(pmcs_rd_gsm_reg(pwp, GSM_FLASH_BASE_UPPER,
            GSM_FLASH_IMG_FLAGS)) & 0xff000000) >> 24;
        if (img_flag & PMCS_IMG_FLAG_A) {
                pwp->fw_active_img = 1;
        } else {
                pwp->fw_active_img = 0;
        }
}