pmcs_attach.c revision c280a92b706bf16eee2a24cc328c9b78d71cb38c
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#define PMCS_DRIVER_VERSION "pmcs HBA device driver"
static char *pmcs_driver_rev = PMCS_DRIVER_VERSION;
/*
* Non-DDI Compliant stuff
*/
extern char hw_serial[];
/*
* Global driver data
*/
void *pmcs_softc_state = NULL;
void *pmcs_iport_softstate = NULL;
/*
* Tracing and Logging info
*/
uint32_t pmcs_tbuf_idx = 0;
static kmutex_t pmcs_trace_lock;
/*
* If pmcs_force_syslog value is non-zero, all messages put in the trace log
* will also be sent to system log.
*/
int pmcs_force_syslog = 0;
int pmcs_console = 0;
/*
* External References
*/
extern int ncpus_online;
/*
* Local static data
*/
static int fwlog_level = 3;
static int physpeed = PHY_LINK_ALL;
static int phymode = PHY_LM_AUTO;
static int block_mask = 0;
#ifdef DEBUG
static int debug_mask = 1;
#else
static int debug_mask = 0;
#endif
#ifdef DISABLE_MSIX
static int disable_msix = 1;
#else
static int disable_msix = 0;
#endif
#ifdef DISABLE_MSI
static int disable_msi = 1;
#else
static int disable_msi = 0;
#endif
/*
* Local prototypes
*/
static int pmcs_unattach(pmcs_hw_t *);
static int pmcs_iport_unattach(pmcs_iport_t *);
static int pmcs_add_more_chunks(pmcs_hw_t *, unsigned long);
static void pmcs_watchdog(void *);
static int pmcs_setup_intr(pmcs_hw_t *);
static int pmcs_teardown_intr(pmcs_hw_t *);
static void pmcs_create_phy_stats(pmcs_iport_t *);
int pmcs_update_phy_stats(kstat_t *, int);
static void pmcs_destroy_phy_stats(pmcs_iport_t *);
/*
* Local configuration data
*/
DEVO_REV, /* devo_rev, */
0, /* refcnt */
ddi_no_info, /* info */
nulldev, /* identify */
nulldev, /* probe */
pmcs_attach, /* attach */
pmcs_detach, /* detach */
nodev, /* reset */
NULL, /* driver operations */
NULL, /* bus operations */
ddi_power, /* power management */
pmcs_quiesce /* quiesce */
};
&pmcs_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
};
const ddi_dma_attr_t pmcs_dattr = {
DMA_ATTR_V0, /* dma_attr version */
0x0000000000000000ull, /* dma_attr_addr_lo */
0xFFFFFFFFFFFFFFFFull, /* dma_attr_addr_hi */
0x00000000FFFFFFFFull, /* dma_attr_count_max */
0x0000000000000001ull, /* dma_attr_align */
0x00000078, /* dma_attr_burstsizes */
0x00000001, /* dma_attr_minxfer */
0x00000000FFFFFFFFull, /* dma_attr_maxxfer */
0x00000000FFFFFFFFull, /* dma_attr_seg */
1, /* dma_attr_sgllen */
512, /* dma_attr_granular */
0 /* dma_attr_flags */
};
static ddi_device_acc_attr_t rattr = {
};
/*
*/
int
_init(void)
{
int ret;
if (ret != 0) {
return (ret);
}
return (ret);
}
/*
* Allocate soft state for iports
*/
sizeof (pmcs_iport_t), 2);
if (ret != 0) {
return (ret);
}
if (ret != 0) {
return (ret);
}
/* Initialize the global trace lock */
return (0);
}
int
_fini(void)
{
int ret;
return (ret);
}
/* Free pmcs log buffer and destroy the global lock */
if (pmcs_tbuf) {
pmcs_tbuf_num_elems * sizeof (pmcs_tbuf_t));
}
return (0);
}
int
{
}
static int
{
char *iport_ua;
char *init_port;
int hba_inst;
int inst;
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
"%s: invoked with NULL unit address, inst (%d)",
return (DDI_FAILURE);
}
"Failed to alloc soft state for iport %d", inst);
return (DDI_FAILURE);
}
"cannot get iport soft state");
goto iport_attach_fail1;
}
/* Set some data on the iport handle */
/* Dup the UA into the iport handle */
/*
* If our unit address is active in the phymap, configure our
* iport's phylist.
*/
if (ua_priv) {
/* Non-NULL private data indicates the unit address is active */
"%s: failed to "
"configure phys on iport handle (0x%p), "
" unit address [%s]", __func__,
goto iport_attach_fail2;
}
} else {
}
/* Allocate string-based soft state pool for targets */
sizeof (pmcs_xscsi_t), PMCS_TGT_SSTATE_SZ) != 0) {
"cannot get iport tgt soft state");
goto iport_attach_fail2;
}
/* Create this iport's target map */
"Failed to create tgtmap on iport %d", inst);
goto iport_attach_fail3;
}
/* Set up the 'initiator-port' DDI property on this iport */
if (pwp->separate_ports) {
"%s: separate ports not supported", __func__);
} else {
/* Set initiator-port value to the HBA's base WWN */
}
/* Set up a 'num-phys' DDI property for the iport node */
/* Create kstats for each of the phys in this port */
/*
* Insert this iport handle into our list and set
* iports_attached on the HBA node.
*/
pwp->num_iports++;
"iport%d attached", inst);
return (DDI_SUCCESS);
/* teardown and fail */
return (DDI_FAILURE);
}
static int
{
int inst, i;
int sm_hba = 1;
int protocol = 0;
int num_phys = 0;
char buf[64];
char *fwl_file;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_PM_RESUME:
case DDI_RESUME:
if (!tran) {
return (DDI_FAILURE);
}
/* No DDI_?_RESUME on iport nodes */
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* If this is an iport node, invoke iport attach.
*/
return (pmcs_iport_attach(dip));
}
/*
* From here on is attach for the HBA node
*/
#ifdef DEBUG
/*
* Check to see if this unit is to be disabled. We can't disable
* on a per-iport node. It's either the entire HBA or nothing.
*/
return (DDI_FAILURE);
}
#endif
/*
* Allocate softstate
*/
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* Create the list for iports
*/
/*
* Get driver.conf properties
*/
}
&fwl_file) == DDI_SUCCESS)) {
"%s%d-iop.0", fwl_file,
}
} else {
}
/* Allocate trace buffer */
if ((pmcs_tbuf_num_elems == DDI_PROP_NOT_FOUND) ||
(pmcs_tbuf_num_elems == 0)) {
}
sizeof (pmcs_tbuf_t), KM_SLEEP);
pmcs_tbuf_idx = 0;
}
"%s: firmware event log files: %s, %s", __func__,
} else {
"%s: No firmware event log will be written "
"(event log disabled)", __func__);
} else {
"%s: No firmware event log will be written "
"(no filename configured - too long?)", __func__);
}
pwp->fwlog_file = 0;
}
if (pwp->fw_force_update == 0) {
"pmcs-fw-disable-update", 0);
}
/*
* Initialize FMA
*/
/*
* Map registers
*/
"pci config setup failed");
return (DDI_FAILURE);
}
/*
* Get the size of register set 3.
*/
"unable to get size of register set %d", PMCS_REGSET_3);
return (DDI_FAILURE);
}
/*
* Map registers
*/
"failed to map Message Unit registers");
return (DDI_FAILURE);
}
"failed to map TOP registers");
return (DDI_FAILURE);
}
"failed to map GSM registers");
return (DDI_FAILURE);
}
"failed to map MPI registers");
return (DDI_FAILURE);
}
/*
* Make sure we can support this card.
*/
case PMCS_PM8001_REV_A:
case PMCS_PM8001_REV_B:
"Rev A/B Card no longer supported");
goto failure;
case PMCS_PM8001_REV_C:
break;
default:
goto failure;
}
/*
* Allocate DMA addressable area for Inbound and Outbound Queue indices
* that the chip needs to access plus a space for scratch usage
*/
goto failure;
}
/*
* Allocate DMA S/G list chunks
*/
/*
* Allocate a DMA addressable area for the firmware log (if needed)
*/
/*
* Align to event log header and entry size
*/
&pwp->fwlog_acchdl,
"Failed to setup DMA for fwlog area");
} else {
}
}
B_FALSE) {
"Failed to setup DMA for register dump area");
goto failure;
}
}
/*
* More bits of local initialization...
*/
"unable to create worker taskq");
goto failure;
}
/*
* Cache of structures for dealing with I/O completion callbacks.
*/
/*
* Cache of PHY structures
*/
NULL, 0);
/*
* Allocate space for the I/O completion threads
*/
if (num_threads > PMCS_MAX_CQ_THREADS) {
}
/*
* Set the quantum value in clock ticks for the I/O interrupt
* coalescing timer.
*/
/*
* We have a delicate dance here. We need to set up
* interrupts so we know how to set up some OQC
* tables. However, while we're setting up table
* access, we may need to flash new firmware and
* reset the card, which will take some finessing.
*/
/*
* Set up interrupts here.
*/
switch (pmcs_setup_intr(pwp)) {
case 0:
break;
case EIO:
/* FALLTHROUGH */
default:
goto failure;
}
/*
* Set these up now becuase they are used to initialize the OQC tables.
*
* If we have MSI or MSI-X interrupts set up and we have enough
* vectors for each OQ, the Outbound Queue vectors can all be the
* same as the appropriate interrupt routine will have been called
* and the doorbell register automatically cleared.
* This keeps us from having to check the Outbound Doorbell register
* when the routines for these interrupts are called.
*
* If we have Legacy INT-X interrupts set up or we didn't have enough
* vectors to the bits we would like to have set in the Outbound
* Doorbell register because pmcs_all_intr will read the doorbell
* register to find out why we have an interrupt and write the
* corresponding 'clear' bit for that interrupt.
*/
case 1:
/*
* Only one vector, so we must check all OQs for MSI. For
* INT-X, there's only one vector anyway, so we can just
* use the outbound queue bits to keep from having to
* check each queue for each interrupt.
*/
} else {
}
break;
case 2:
/* With 2, we can at least isolate IODONE */
break;
case 4:
/* With 4 vectors, everybody gets one */
break;
}
/*
* Do the first part of setup
*/
if (pmcs_setup(pwp)) {
goto failure;
}
/*
* Now do some additonal allocations based upon information
* gathered during MPI setup.
*/
}
phyp++;
}
}
/*
* Start MPI communication.
*/
if (pmcs_start_mpi(pwp)) {
goto failure;
}
}
/*
* Do some initial acceptance tests.
* This tests interrupts and queues.
*/
if (pmcs_echo_test(pwp)) {
goto failure;
}
/* Read VPD - if it exists */
"%s: Unable to read VPD: "
"attempting to fabricate", __func__);
/*
* When we release, this must goto failure and the call
* to pmcs_fabricate_wwid is removed.
*/
/* goto failure; */
if (!pmcs_fabricate_wwid(pwp)) {
goto failure;
}
}
/*
* We're now officially running
*/
/*
* Check firmware versions and load new firmware
* if needed and reset.
*/
if (pmcs_firmware_update(pwp)) {
"%s: Firmware update failed", __func__);
goto failure;
}
/*
* Create completion threads.
*/
}
/*
* Create one thread to deal with the updating of the interrupt
* coalescing timer.
*/
/*
* Kick off the watchdog
*/
/*
* Do the SCSI attachment code (before starting phys)
*/
goto failure;
}
/*
* Initialize the rwlock for the iport elements.
*/
/* Check all acc & dma handles allocated in attach */
if (pmcs_check_acc_dma_handle(pwp)) {
goto failure;
}
/*
* Create the phymap for this HBA instance
*/
goto failure;
}
/*
* Create the iportmap for this HBA instance
*/
goto failure;
}
/*
* Start the PHYs.
*/
if (pmcs_start_phys(pwp)) {
goto failure;
}
/*
* From this point on, we can't fail.
*/
/* SM-HBA */
&sm_hba);
/* SM-HBA */
/* SM-HBA */
hw_rev);
/* SM-HBA */
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;
}
fw_rev);
/* SM-HBA */
&num_phys);
/* SM-HBA */
&protocol);
return (DDI_SUCCESS);
if (pmcs_unattach(pwp)) {
}
return (DDI_FAILURE);
}
int
{
/* iport node */
return (DDI_FAILURE);
}
} else {
/* hba node */
return (DDI_FAILURE);
}
}
switch (cmd) {
case DDI_DETACH:
if (iport) {
/* iport detach */
if (pmcs_iport_unattach(iport)) {
return (DDI_FAILURE);
}
"iport%d detached", inst);
return (DDI_SUCCESS);
} else {
/* HBA detach */
if (pmcs_unattach(pwp)) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
case DDI_SUSPEND:
case DDI_PM_SUSPEND:
/* No DDI_SUSPEND on iport nodes */
if (iport) {
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
if (!tran) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
{
/*
* First, check if there are still any configured targets on this
* iport. If so, we fail detach.
*/
"iport%d detach failure: iport has targets (luns)",
return (DDI_FAILURE);
}
/*
* Remove this iport from our list if it is inactive in the phymap.
*/
"iport%d detach failure: "
"iport unit address active in phymap",
return (DDI_FAILURE);
}
/* If it's our only iport, clear iports_attached */
if (--pwp->num_iports == 0) {
pwp->iports_attached = 0;
}
/*
* We have removed the iport handle from the HBA's iports list,
* there will be no new references to it. Two things must be
* guarded against here. First, we could have PHY up events,
* adding themselves to the iport->phys list and grabbing ref's
* on our iport handle. Second, we could have existing references
* to this iport handle from a point in time prior to the list
* removal above.
*
* So first, destroy the phys list. Remove any phys that have snuck
* in after the phymap deactivate, dropping the refcnt accordingly.
* If these PHYs are still up if and when the phymap reactivates
* (i.e. when this iport reattaches), we'll populate the list with
* them and bump the refcnt back up.
*/
/*
* Second, wait for any other references to this iport to be
* dropped, then continue teardown.
*/
}
/* Delete kstats */
/* Destroy the iport target map */
return (DDI_FAILURE);
}
/* Free the tgt soft state */
}
/* Free our unit address string */
/* Finish teardown and free the softstate */
return (DDI_SUCCESS);
}
static int
{
int i;
/*
* Tear down the interrupt infrastructure.
*/
if (pmcs_teardown_intr(pwp)) {
}
/*
* Grab a lock, if initted, to set state.
*/
if (pwp->locks_initted) {
}
/*
* Stop the I/O completion threads.
*/
}
}
/*
* Stop the interrupt coalescing timer thread
*/
if (pwp->ict_thread) {
}
} else {
}
}
/* Destroy the iports lock */
/* Destroy the iports list */
}
/* Destroy the iportmap */
}
/* Destroy the phymap */
}
/*
* Make sure that any pending watchdog won't
* be called from this point on out.
*/
/*
* After the above action, the watchdog
* timer that starts up the worker task
* may trigger but will exit immediately
* on triggering.
*
* Now that this is done, we can destroy
* the task queue, which will wait if we're
* running something on it.
*/
}
if (pwp->hba_attached) {
pwp->hba_attached = 0;
}
/*
* If the chip hasn't been marked dead, shut it down now
* to bring it back to a known state without attempting
* a soft reset.
*/
/*
* De-register all registered devices
*/
/*
* Stop all the phys.
*/
/*
* Shut Down Message Passing
*/
(void) pmcs_stop_mpi(pwp);
/*
* Reset chip
*/
}
/*
* Turn off interrupts on the chip
*/
if (pwp->mpi_acc_handle) {
}
/* Destroy pwp's lock */
if (pwp->locks_initted) {
#ifdef DEBUG
#endif
pwp->locks_initted = 0;
}
/*
* Free DMA handles and associated consistent memory
*/
if (pwp->regdump_hndl) {
"Condition check failed "
}
pwp->regdump_hndl = 0;
}
if (pwp->fwlog_hndl) {
"Condition check failed "
}
pwp->fwlog_hndl = 0;
}
if (pwp->cip_handles) {
"Condition check failed "
}
pwp->cip_handles = 0;
}
for (i = 0; i < PMCS_NOQ; i++) {
if (pwp->oqp_handles[i]) {
DDI_SUCCESS) {
"Condition check failed at %s():%d",
}
pwp->oqp_handles[i] = 0;
}
}
for (i = 0; i < PMCS_NIQ; i++) {
if (pwp->iqp_handles[i]) {
DDI_SUCCESS) {
"Condition check failed at %s():%d",
}
pwp->iqp_handles[i] = 0;
}
}
/*
* Unmap registers and destroy access handles
*/
if (pwp->mpi_acc_handle) {
pwp->mpi_acc_handle = 0;
}
if (pwp->top_acc_handle) {
pwp->top_acc_handle = 0;
}
if (pwp->gsm_acc_handle) {
pwp->gsm_acc_handle = 0;
}
if (pwp->msg_acc_handle) {
pwp->msg_acc_handle = 0;
}
if (pwp->pci_acc_handle) {
pwp->pci_acc_handle = 0;
}
/*
* Do memory allocation cleanup.
*/
while (pwp->dma_freelist) {
}
/*
* Free pools
*/
if (pwp->iocomp_cb_cache) {
}
/*
* Free all PHYs (at level > 0), then free the cache
*/
}
/*
* Free root PHYs
*/
}
}
/* Free the targets list */
}
/*
* Free work structures
*/
}
}
/*
* Do last property and SCSA cleanup
*/
}
if (pwp->reset_notify_listf) {
}
return (-1);
}
/* Free register dump area if allocated */
}
}
}
return (0);
}
/*
* quiesce (9E) entry point
*
* This function is called when the system is single-threaded at high PIL
*
* Returns DDI_SUCCESS or DDI_FAILURE.
*
*/
static int
{
return (DDI_SUCCESS);
/* No quiesce necessary on a per-iport basis */
return (DDI_SUCCESS);
}
return (DDI_SUCCESS);
/* Stop MPI & Reset chip (no need to re-initialize) */
(void) pmcs_stop_mpi(pwp);
return (DDI_SUCCESS);
}
/*
* Called with xp->statlock and PHY lock and scratch acquired.
*/
static int
{
int result, i;
uint16_t *a;
union {
} u;
/*
* Safe defaults - use only if this target is brand new (i.e. doesn't
* already have these settings configured)
*/
}
/*
* We only try and issue an IDENTIFY for first level
* (direct attached) devices. We don't try and
* set other quirks here (this will happen later,
* if the device is fully configured)
*/
return (0);
}
if (result) {
return (result);
}
for (i = 0; i < 4; i++) {
u.nsb[i] = ddi_swap16(*a++);
}
/*
* Check the returned data for being a valid (NAA=5) WWN.
* If so, use that and override the SAS address we were
* given at Link Up time.
*/
}
"%s: %s has SAS ADDRESS " SAS_ADDR_FMT,
return (0);
}
/*
* Called with PHY lock and target statlock held and scratch acquired
*/
static boolean_t
{
case SATA:
target, "%s: add_sata_device failed for tgt 0x%p",
return (B_FALSE);
}
break;
case SAS:
break;
case EXPANDER:
break;
}
/*
* Set the PHY's config stop time to 0. This is one of the final
* stops along the config path, so we're indicating that we
* successfully configured the PHY.
*/
return (B_TRUE);
}
void
pmcs_worker(void *arg)
{
return;
}
if (work_flags & PMCS_WORK_FLAG_DUMP_REGS) {
}
if (work_flags & PMCS_WORK_FLAG_SAS_HW_ACK) {
}
if (work_flags & PMCS_WORK_FLAG_SPINUP_RELEASE) {
}
}
if (work_flags & PMCS_WORK_FLAG_DS_ERR_RECOVERY) {
}
if (work_flags & PMCS_WORK_FLAG_DEREGISTER_DEV) {
}
if (work_flags & PMCS_WORK_FLAG_DISCOVER) {
}
if (work_flags & PMCS_WORK_FLAG_ABORT_HANDLE) {
if (pmcs_abort_handler(pwp)) {
}
}
if (work_flags & PMCS_WORK_FLAG_SATA_RUN) {
}
if (work_flags & PMCS_WORK_FLAG_RUN_QUEUES) {
}
if (work_flags & PMCS_WORK_FLAG_ADD_DMA_CHUNKS) {
if (pmcs_add_more_chunks(pwp,
} else {
}
}
}
static int
{
unsigned long dl;
"Not enough memory for DMA chunks");
return (-1);
}
"Failed to setup DMA for chunks");
return (-1);
}
return (-1);
}
}
return (0);
}
static void
{
int i;
return;
}
/*
* Ensure that inbound work is getting picked up. First, check to
* see if new work has been posted. If it has, ensure that the
* work is moving forward by checking the consumer index and the
* last_htag for the work being processed against what we saw last
* time. Note: we use the work structure's 'last_htag' because at
* any given moment it could be freed back, thus clearing 'htag'
* and setting 'last_htag' (see pmcs_pwork).
*/
for (i = 0; i < PMCS_NIQ; i++) {
continue;
}
"Inbound Queue stall detected, issuing reset");
goto hot_reset;
}
}
/*
* Check heartbeat on both the MSGU and IOP. It is unlikely that
* we'd ever fail here, as the inbound queue monitoring code above
* would detect a stall due to either of these elements being
* stalled, but we might as well keep an eye on them.
*/
"Stall detected on MSGU, issuing reset");
goto hot_reset;
}
"Stall detected on IOP, issuing reset");
goto hot_reset;
}
return;
/*
* We've detected a stall. Attempt to recover service via hot
* reset. In case of failure, pmcs_hot_reset() will handle the
* failure and issue any required FM notifications.
* See pmcs_subr.c for more details.
*/
if (pmcs_hot_reset(pwp)) {
"%s: hot reset failure", __func__);
} else {
"%s: hot reset complete", __func__);
}
}
static void
{
pmcs_cmd_t *sp;
char path[32];
int rval;
/*
* If the command isn't active, we can't be timing it still.
* Active means the tag is not free and the state is "on chip".
*/
if (!PMCS_COMMAND_ACTIVE(pwrk)) {
continue;
}
/*
* No timer active for this command.
*/
continue;
}
/*
* Knock off bits for the time interval.
*/
} else {
}
continue;
}
/*
* The command has now officially timed out.
* Get the path for it. If it doesn't have
* a phy pointer any more, it's really dead
* and can just be put back on the free list.
* There should *not* be any commands associated
* with it any more.
*/
"dead command with gone phy being recycled");
continue;
}
/*
* If this is a non-SCSA command, stop here. Eventually
* we might do something with non-SCSA commands here-
* but so far their timeout mechanisms are handled in
* the WAIT_FOR macro.
*/
"%s: non-SCSA cmd tag 0x%x timed out",
continue;
}
/*
* Mark it as timed out.
*/
#ifdef DEBUG
"%s: SCSA cmd tag 0x%x timed out (state %x) onwire=%d",
#else
"%s: SCSA cmd tag 0x%x timed out (state %x)",
#endif
/*
* Mark the work structure as timed out.
*/
/*
* No point attempting recovery if the device is gone
*/
"%s: tgt(0x%p) is gone. Returning CMD_DEV_GONE "
"for htag 0x%08x", __func__,
if (!PMCS_COMMAND_DONE(pwrk)) {
/* Complete this command here */
"%s: Completing cmd (htag 0x%08x) "
} else {
}
continue;
}
if (rval) {
"%s: Bad status (%d) on abort of HTAG 0x%08x",
if (!PMCS_COMMAND_DONE(pwrk)) {
/* Complete this command here */
"%s: Completing cmd (htag 0x%08x) "
}
} else {
}
/*
* No need to reschedule ABORT if we get any other
* status
*/
phyp->abort_sent = 0;
}
}
}
/*
* Run any completions that may have been queued up.
*/
}
static void
pmcs_watchdog(void *arg)
{
/*
* Check forward progress on the chip
*/
pwp->watchdog_count = 0;
}
/*
* Check to see if we need to kick discovery off again
*/
if (pwp->config_restart &&
"%s: Timer expired for re-enumeration: Start discovery",
__func__);
}
return;
}
DDI_NOSLEEP) != DDI_SUCCESS) {
"Could not dispatch to worker thread");
}
}
}
static int
{
int i, r, rslt = 0;
for (i = 0; i < icnt; i++) {
if (r == DDI_SUCCESS) {
continue;
}
"%s: unable to remove interrupt handler %d", __func__, i);
rslt = -1;
break;
}
return (rslt);
}
static int
{
if (r != DDI_SUCCESS) {
"unable to disable interrupt block");
return (-1);
}
} else {
int i;
for (i = 0; i < icnt; i++) {
continue;
}
"unable to disable interrupt %d", i);
return (-1);
}
}
return (0);
}
static int
{
int i;
for (i = 0; i < icnt; i++) {
continue;
}
"unable to free interrupt %d", i);
return (-1);
}
pwp->ih_table_size = 0;
return (0);
}
/*
* Try to set up interrupts of type "type" with a minimum number of interrupts
* of "min".
*/
static void
{
"%s: get_nintrs failed; type: %d rc: %d count: %d min: %d",
return;
}
"%s: get_navail failed; type: %d rc: %d avail: %d min: %d",
return;
}
switch (type) {
case DDI_INTR_TYPE_MSIX:
max = PMCS_MAX_MSIX;
break;
case DDI_INTR_TYPE_MSI:
max = PMCS_MAX_MSI;
break;
case DDI_INTR_TYPE_FIXED:
default:
break;
}
if (rval != DDI_SUCCESS) {
"%s: ddi_intr_alloc failed; type: %d rc: %d",
pwp->ih_table_size = 0;
return;
}
}
/*
* Set up interrupts.
* We return one of three values:
*
* 0 - success
* EAGAIN - failure to set up interrupts
* EIO - "" + we're now stuck partly enabled
*
* If EIO is returned, we can't unload the driver.
*/
static int
{
"cannot get interrupt types");
return (EAGAIN);
}
if (disable_msix) {
itypes &= ~DDI_INTR_TYPE_MSIX;
}
if (disable_msi) {
itypes &= ~DDI_INTR_TYPE_MSI;
}
/*
* We won't know what firmware we're running until we call pmcs_setup,
* and we can't call pmcs_setup until we establish interrupts.
*/
/*
* We want PMCS_MAX_MSIX vectors for MSI-X. Anything less would be
* uncivilized.
*/
if (itypes & DDI_INTR_TYPE_MSIX) {
itypes = 0;
}
}
if (itypes & DDI_INTR_TYPE_MSI) {
itypes = 0;
}
}
if (itypes & DDI_INTR_TYPE_FIXED) {
itypes = 0;
}
}
"No interrupts available");
return (EAGAIN);
}
/*
* Get iblock cookie and add handlers.
*/
case 1:
iv_table[0] = pmcs_all_intr;
break;
case 2:
iv_table[0] = pmcs_iodone_ix;
break;
case 4:
break;
default:
return (EAGAIN);
}
if (r != DDI_SUCCESS) {
if (pmcs_remove_ihandlers(pwp, i)) {
return (EIO);
}
if (pmcs_free_intrs(pwp, i)) {
return (EIO);
}
return (EAGAIN);
}
}
"unable to get int capabilities");
return (EIO);
}
return (EIO);
}
return (EAGAIN);
}
if (r != DDI_SUCCESS) {
"intr blk enable failed");
return (EIO);
}
return (EIO);
}
return (EFAULT);
}
} else {
if (r == DDI_SUCCESS) {
continue;
}
"unable to enable interrupt %d", i);
if (pmcs_disable_intrs(pwp, i)) {
return (EIO);
}
return (EIO);
}
return (EIO);
}
return (EAGAIN);
}
}
/*
* Set up locks.
*/
"unable to get interrupt priority");
return (EIO);
}
return (EIO);
}
return (EIO);
}
return (EAGAIN);
}
DDI_INTR_PRI(pri));
#ifdef DEBUG
#endif
for (i = 0; i < PMCS_NIQ; i++) {
}
}
/*
* Enable Interrupts
*/
} else {
}
pri ^= (1 << i);
}
return (0);
}
static int
{
return (EIO);
}
return (EIO);
}
return (EIO);
}
}
return (0);
}
static uint_t
{
return (DDI_INTR_CLAIMED);
}
static uint_t
{
return (DDI_INTR_CLAIMED);
}
static uint_t
{
/*
* It's possible that if we just turned interrupt coalescing off
* (and thus, re-enabled auto clear for interrupts on the I/O outbound
* queue) that there was an interrupt already pending. We use
* io_intr_coal.int_cleared to ensure that we still drop in here and
* clear the appropriate interrupt bit one last time.
*/
(1 << PMCS_OQ_IODONE));
}
return (DDI_INTR_CLAIMED);
}
static uint_t
{
return (DDI_INTR_CLAIMED);
}
static uint_t
{
/*
* Check for Fatal Interrupts
*/
return (DDI_INTR_CLAIMED);
}
(1 << PMCS_OQ_GENERAL));
}
return (DDI_INTR_CLAIMED);
}
static uint_t
{
int handled = 0;
/*
* Check for Fatal Interrupts
*/
return (DDI_INTR_CLAIMED);
}
/*
* Check for Outbound Queue service needed
*/
(1 << PMCS_OQ_IODONE));
handled++;
}
(1 << PMCS_OQ_GENERAL));
handled++;
}
(1 << PMCS_OQ_EVENTS));
handled++;
}
if (obdb) {
"interrupt bits not handled (0x%x)", obdb);
handled++;
}
handled++;
}
}
void
{
/*
* Attempt a hot reset. In case of failure, pmcs_hot_reset() will
* handle the failure and issue any required FM notifications.
* See pmcs_subr.c for more details.
*/
if (pmcs_hot_reset(pwp)) {
"%s: hot reset failure", __func__);
} else {
"%s: hot reset complete", __func__);
}
}
/*
* Called with PHY lock and target statlock held and scratch acquired.
*/
{
case SAS:
case EXPANDER:
break;
case SATA:
break;
default:
"%s: Target %p has PHY %p with invalid dtype",
return (B_FALSE);
}
tgt->recover_wait = 0;
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Called with softstate lock held
*/
void
{
unsigned int vtgt;
continue;
}
"cancel config of vtgt %u", vtgt);
} else {
"Removed tgt 0x%p vtgt %u",
}
break;
}
}
}
void
{
int written = 0;
char *ptr;
int system_log_level;
switch (level) {
case PMCS_PRT_DEBUG_DEVEL:
case PMCS_PRT_DEBUG_DEV_STATE:
case PMCS_PRT_DEBUG_UNDERFLOW:
case PMCS_PRT_DEBUG_CONFIG:
case PMCS_PRT_DEBUG_IPORT:
case PMCS_PRT_DEBUG_MAP:
case PMCS_PRT_DEBUG3:
case PMCS_PRT_DEBUG2:
case PMCS_PRT_DEBUG1:
case PMCS_PRT_DEBUG:
break;
case PMCS_PRT_INFO:
system_log = B_TRUE;
break;
case PMCS_PRT_WARN:
system_log = B_TRUE;
break;
case PMCS_PRT_ERR:
system_log = B_TRUE;
break;
default:
return;
}
/*
* Store the pertinent PHY and target information if there is any
*/
} else {
}
} else {
}
/* Indicate truncation */
}
if (++pmcs_tbuf_idx == pmcs_tbuf_num_elems) {
pmcs_tbuf_idx = 0;
} else {
}
/*
* When pmcs_force_syslog in non-zero, everything goes also
* to syslog, at CE_CONT level.
*/
if (pmcs_force_syslog) {
system_log = B_TRUE;
}
/*
* Anything that comes in with PMCS_PRT_INFO, WARN, or ERR also
* goes to syslog.
*/
if (system_log) {
char local[196];
switch (system_log_level) {
case CE_CONT:
break;
case CE_NOTE:
case CE_WARN:
local[0] = 0;
break;
default:
return;
}
}
}
}
/*
* pmcs_acquire_scratch
*
* If "wait" is true, the caller will wait until it can acquire the scratch.
* This implies the caller needs to be in a context where spinning for an
* indeterminate amount of time is acceptable.
*/
int
{
int rval;
if (!wait) {
}
/*
* Caller will wait for scratch.
*/
drv_usecwait(100);
}
return (rval);
}
void
{
pwp->scratch_locked = 0;
}
static void
{
int ndata;
char ks_name[KSTAT_STRLEN];
/* We've already created this kstat instance */
continue;
}
KSTAT_TYPE_NAMED, ndata, 0);
"%s: Failed to create %s kstats", __func__,
ks_name);
continue;
}
"SecondsSinceLastReset", KSTAT_DATA_ULONGLONG);
"TxFrames", KSTAT_DATA_ULONGLONG);
"RxFrames", KSTAT_DATA_ULONGLONG);
"TxWords", KSTAT_DATA_ULONGLONG);
"RxWords", KSTAT_DATA_ULONGLONG);
"InvalidDwordCount", KSTAT_DATA_ULONGLONG);
"RunningDisparityErrorCount", KSTAT_DATA_ULONGLONG);
"LossofDwordSyncCount", KSTAT_DATA_ULONGLONG);
"PhyResetProblemCount", KSTAT_DATA_ULONGLONG);
}
}
int
{
/*
* We just want to lock against other invocations of kstat;
* we don't need to pmcs_lock_phy() for this.
*/
/* Get Stats from Chip */
if (val == DDI_FAILURE)
goto fail;
if (val == DDI_FAILURE)
goto fail;
if (val == DDI_FAILURE)
goto fail;
if (val == DDI_FAILURE)
goto fail;
ret = DDI_SUCCESS;
fail:
return (ret);
}
static void
{
return;
}
}
}
/*ARGSUSED*/
static int
{
/*
* as the driver can always deal with an error in any dma or
* access handle, we can just return the fme_status value.
*/
return (err->fme_status);
}
static void
{
/* Only register with IO Fault Services if we have some capability */
if (pwp->fm_capabilities) {
/* Adjust access and dma attributes for FMA */
/*
* Register capabilities with IO Fault Services.
*/
/*
* Initialize pci ereport capabilities if ereport
* capable (should always be.)
*/
}
/*
* Register error callback if error callback capable.
*/
pmcs_fm_error_cb, (void *) pwp);
}
}
}
static void
{
/* Only unregister FMA capabilities if registered */
if (pwp->fm_capabilities) {
/*
* Un-register error callback if error callback capable.
*/
}
/*
* Release any resources allocated by pci_ereport_setup()
*/
}
/* Unregister from IO Fault Services */
/* Adjust access and dma attributes for FMA */
}
}
static boolean_t
{
char *cp, c;
int i;
cp = &c;
if (adr == 0) {
"%s: No serial number available to fabricate WWN",
__func__);
}
adr <<= 8;
for (i = 0; i < PMCS_MAX_PORTS; i++) {
}
return (B_TRUE);
}