/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2017, Joyent, Inc. */ #include /* * Discovery, Resets, Periodics, and Events * ---------------------------------------- * * Discovery is the act of figuring out what logical and physical volumes exist * under the controller. Discovery happens in response to the following events: * * o iports for virtual and physical devices being attached * o Controller event notifications indicating potential topology changes * o After a reset of the controller, before we can perform I/O again * * Because we have to perform discovery after a reset, which can happen during * panic(), that also means that discovery may be run in panic context. We * also need to emphasize the need for discovery to happen after a controller * reset. Once a reset is initiated, we cannot be certain about the addresses * of any of the existing targets until the reset has completed. The driver * performs I/Os to addresses that the controller provides. The controller * specification says that these addresses may change after a controller reset. * * Unfortunately, all of this combined means that making sure we can correctly * run discovery is somewhat complicated. In non-panic contexts, discovery is * always run from a taskq. We'll kick off the discovery in the taskq if * nothing is pending at that time. The state is managed by bits in the * smrt_status member of the smrt_t. There are four bits at this time: * * SMRT_CTLR_DISCOVERY_REQUESTED This flag indicates that something has * requested that a discovery be performed. * If no flags are set when this is set, * then we will kick off discovery. All * discovery requests are initiated via the * smrt_discover_request() function. * * SMRT_CTLR_DISCOVERY_RUNNING This flag is set at the start of us * running a discovery. It is removed when * discovery finishes. * * SMRT_CTLR_DISCOVERY_PERIODIC This flag is set in a number of * circumstances, which will be described * in a subsequent section. This indicates * that the periodic must kick off the * discovery process. * * SMRT_CTLR_DISCOVERY_REQUIRED This flag indicates that at some point a * controller reset occurred and we need to * have a successful discovery to finish * the act of resetting and allowing I/O to * continue. * * In general, a request to discover kicks off the taskq to discover entries, if * it hasn't already been requested or started. This also allows us to coalesce * multiple requests, if needed. Note that if a request comes in when a * discovery is ongoing, we do not kick off discovery again. Instead, we set * the SMRT_CTLR_DISCOVERY_REQUESTED flag which will rerun discovery after the * initial pass has completed. * * When a discovery starts, the first thing it does is clear the * SMRT_CTLR_DISCOVERY_REQUESTED flag. This is important, because any * additional requests for discovery that come in after this has started likely * indicate that we've missed something. As such, when the discovery process * finishes, if it sees the REQUESTED flag, then it will need to set the * PERIODIC flag. The PERIODIC flag is used to indicate that we should run * discovery again, but not kick if off immediately. Instead, it should be * driven by the normal periodic behavior. * * If for some reason the act of discovery fails, or we fail to dispatch * discovery due to a transient error, then we will flag PERIODIC so that the * periodic tick will try and run things again. * * Now, we need to talk about SMRT_CTLR_DISCOVERY_REQUIRED. This flag is set * after a reset occurs. The reset thread will be blocked on this. * Importantly, none of the code in the discovery path can ask for a controller * reset at this time. If at the end of a discovery, this flag is set, then we * will signal the reset thread that it should check on its status by * broadcasting on the smrt_cv_finishq. At that point, the reset thread will * continue. * * Panic Context * ------------- * * All of this talk of threads and taskqs is well and good, but as an HBA * driver, we have a serious responsibility to try and deal with panic sanely. * In panic context, we will directly call the discovery functions and not poll * for them to occur. * * However, because our discovery relies on the target maps, which aren't safe * for panic context at this time, we have to take a different approach. We * leverage the fact that we have a generation number stored with every * discovery. If we try to do an I/O to a device where the generation doesn't * match, then we know that it disappeared and should not be used. We also * sanity check the model, serial numbers, and WWNs to make sure that these are * the same devices. If they are, then we'll end up updating the address * structures. * * Now, it is possible that when we were panicking, we had a thread that was in * the process of running a discovery or even resetting the system. Once we're * in panic, those threads aren't running, so if they didn't end up producing a * new view of the world that the SCSI framework is using, then it shouldn't * really matter, as we won't have updated the list of devices. Importantly, * once we're in that context, we're not going to be attaching or detaching * targets. If we get a request for one of these targets which has disappeared, * we're going to have to end up giving up. * * Request Attributes * ------------------ * * The CISS specification allows for three different kinds of attributes that * describe how requests are queued to the controller. These are: * * HEAD OF QUEUE The request should go to the head of the * controller queue. This is used for resets and * aborts to ensure that they're not blocked behind * additional I/O. * * SIMPLE This queues the request for normal processing. * Commands queued this way are not special with * respect to one another. We use this for all I/O * and discovery commands. * * ORDERED This attribute is used to indicate that commands * should be submitted and processed in some order. * This is used primarily for the event * notification bits so we can ensure that at the * return of a cancellation of the event * notification, that any outstanding request has * been honored. */ static int smrt_ctlr_versions(smrt_t *, uint16_t, smrt_versions_t *); static void smrt_discover(void *); /* * The maximum number of seconds to wait for the controller to come online. */ unsigned smrt_ciss_init_time = 90; /* * A tunable that determines the number of events per tick that we'll process * via asynchronous event notification. If this rate is very high, then we will * not submit the event and it will be picked up at the next tick of the * periodic. */ uint_t smrt_event_intervention_threshold = 1000; /* * Converts a LUN Address to a BMIC Identifier. The BMIC Identifier is used * when performing various physical commands and generally should stay the same * for a given device across inserts and removals; however, not across * controller resets. These are calculated based on what the CISS specification * calls the 'Level 2' target and bus, which don't have a real meaning in the * SAS world otherwise. */ uint16_t smrt_lun_addr_to_bmic(PhysDevAddr_t *paddr) { uint16_t id; id = (paddr->Target[1].PeripDev.Bus - 1) << 8; id += paddr->Target[1].PeripDev.Dev; return (id); } void smrt_write_lun_addr_phys(LUNAddr_t *lun, boolean_t masked, unsigned bus, unsigned target) { lun->PhysDev.Mode = masked ? MASK_PERIPHERIAL_DEV_ADDR : PERIPHERIAL_DEV_ADDR; lun->PhysDev.TargetId = target; lun->PhysDev.Bus = bus; bzero(&lun->PhysDev.Target, sizeof (lun->PhysDev.Target)); } /* * According to the CISS Specification, the controller is always addressed in * Mask Perhiperhal mode with a bus and target ID of zero. This is used by * commands that need to write to the controller itself, which is generally * discovery and other commands. */ void smrt_write_controller_lun_addr(LUNAddr_t *lun) { smrt_write_lun_addr_phys(lun, B_TRUE, 0, 0); } void smrt_write_message_common(smrt_command_t *smcm, uint8_t type, int timeout_secs) { switch (type) { case CISS_MSG_ABORT: case CISS_MSG_RESET: case CISS_MSG_NOP: break; default: panic("unknown message type"); } smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_MSG; smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_HEADOFQUEUE; smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_NONE; smcm->smcm_va_cmd->Request.Timeout = LE_16(timeout_secs); smcm->smcm_va_cmd->Request.CDBLen = CISS_CDBLEN; smcm->smcm_va_cmd->Request.CDB[0] = type; } void smrt_write_message_abort_one(smrt_command_t *smcm, uint32_t tag) { smrt_tag_t cisstag; /* * When aborting a particular command, the request is addressed * to the controller. */ smrt_write_lun_addr_phys(&smcm->smcm_va_cmd->Header.LUN, B_TRUE, 0, 0); smrt_write_message_common(smcm, CISS_MSG_ABORT, 0); /* * Abort a single command. */ smcm->smcm_va_cmd->Request.CDB[1] = CISS_ABORT_TASK; /* * The CISS Specification says that the tag value for a task-level * abort should be in the CDB in bytes 4-11. */ bzero(&cisstag, sizeof (cisstag)); cisstag.tag_value = tag; bcopy(&cisstag, &smcm->smcm_va_cmd->Request.CDB[4], sizeof (cisstag)); } void smrt_write_message_abort_all(smrt_command_t *smcm, LUNAddr_t *addr) { /* * When aborting all tasks for a particular Logical Volume, * the command is addressed not to the controller but to * the Volume itself. */ smcm->smcm_va_cmd->Header.LUN = *addr; smrt_write_message_common(smcm, CISS_MSG_ABORT, 0); /* * Abort all commands for a particular Logical Volume. */ smcm->smcm_va_cmd->Request.CDB[1] = CISS_ABORT_TASKSET; } void smrt_write_message_event_notify(smrt_command_t *smcm) { smrt_event_notify_req_t senr; smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN); smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_CMD; smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_ORDERED; smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_READ; smcm->smcm_va_cmd->Request.Timeout = 0; smcm->smcm_va_cmd->Request.CDBLen = sizeof (senr); bzero(&senr, sizeof (senr)); senr.senr_opcode = CISS_SCMD_READ; senr.senr_subcode = CISS_BMIC_NOTIFY_ON_EVENT; senr.senr_flags = BE_32(0); senr.senr_size = BE_32(SMRT_EVENT_NOTIFY_BUFLEN); bcopy(&senr, &smcm->smcm_va_cmd->Request.CDB[0], MIN(CISS_CDBLEN, sizeof (senr))); } void smrt_write_message_cancel_event_notify(smrt_command_t *smcm) { smrt_event_notify_req_t senr; smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN); smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_CMD; smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_ORDERED; smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_WRITE; smcm->smcm_va_cmd->Request.Timeout = LE_16(SMRT_ASYNC_CANCEL_TIMEOUT); smcm->smcm_va_cmd->Request.CDBLen = sizeof (senr); bzero(&senr, sizeof (senr)); senr.senr_opcode = CISS_SCMD_WRITE; senr.senr_subcode = CISS_BMIC_NOTIFY_ON_EVENT_CANCEL; senr.senr_size = BE_32(SMRT_EVENT_NOTIFY_BUFLEN); bcopy(&senr, &smcm->smcm_va_cmd->Request.CDB[0], MIN(CISS_CDBLEN, sizeof (senr))); } void smrt_write_message_reset_ctlr(smrt_command_t *smcm) { smrt_write_lun_addr_phys(&smcm->smcm_va_cmd->Header.LUN, B_TRUE, 0, 0); smrt_write_message_common(smcm, CISS_MSG_RESET, 0); smcm->smcm_va_cmd->Request.CDB[1] = CISS_RESET_CTLR; } void smrt_write_message_nop(smrt_command_t *smcm, int timeout_secs) { /* * No-op messages are always sent to the controller. */ smrt_write_lun_addr_phys(&smcm->smcm_va_cmd->Header.LUN, B_TRUE, 0, 0); smrt_write_message_common(smcm, CISS_MSG_NOP, timeout_secs); } /* * This routine is executed regularly by ddi_periodic_add(9F). It checks the * health of the controller and looks for submitted commands that have timed * out. */ void smrt_periodic(void *arg) { smrt_t *smrt = arg; mutex_enter(&smrt->smrt_mutex); /* * Before we even check if the controller is running to process * everything else, we must first check if we had a request to kick off * discovery. We do this before the check if the controller is running, * as this may be required to finish a discovery. */ if ((smrt->smrt_status & SMRT_CTLR_DISCOVERY_PERIODIC) != 0 && (smrt->smrt_status & SMRT_CTLR_DISCOVERY_RUNNING) == 0 && (smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) == 0) { if (ddi_taskq_dispatch(smrt->smrt_discover_taskq, smrt_discover, smrt, DDI_NOSLEEP) != DDI_SUCCESS) { smrt->smrt_stats.smrts_discovery_tq_errors++; } else { smrt->smrt_status &= ~SMRT_CTLR_DISCOVERY_PERIODIC; } } if (!(smrt->smrt_status & SMRT_CTLR_STATUS_RUNNING)) { /* * The device is currently not active, e.g. due to an * in-progress controller reset. */ mutex_exit(&smrt->smrt_mutex); return; } /* * Check on the health of the controller firmware. Note that if the * controller has locked up, this routine will panic the system. */ smrt_lockup_check(smrt); /* * Reset the event notification threshold counter. */ smrt->smrt_event_count = 0; /* * Check inflight commands to see if they have timed out. */ for (smrt_command_t *smcm = avl_first(&smrt->smrt_inflight); smcm != NULL; smcm = AVL_NEXT(&smrt->smrt_inflight, smcm)) { if (smcm->smcm_status & SMRT_CMD_STATUS_POLLED) { /* * Polled commands are timed out by the polling * routine. */ continue; } if (smcm->smcm_status & SMRT_CMD_STATUS_ABORT_SENT) { /* * This command has been aborted; either it will * complete or the controller will be reset. */ continue; } if (list_link_active(&smcm->smcm_link_abort)) { /* * Already on the abort queue. */ continue; } if (smcm->smcm_expiry == 0) { /* * This command has no expiry time. */ continue; } if (gethrtime() > smcm->smcm_expiry) { list_insert_tail(&smrt->smrt_abortq, smcm); smcm->smcm_status |= SMRT_CMD_STATUS_TIMEOUT; } } /* * Process the abort queue. */ (void) smrt_process_abortq(smrt); /* * Check if we have an outstanding event intervention request. Note, * the command in question should always be in a state such that it is * usable by the system here. The command is always prepared again by * the normal event notification path, even if a reset has occurred. * The reset will be processed before we'd ever consider running an * event again. Note, if we fail to submit this, then we leave this for * the next occurrence of the periodic. */ if (smrt->smrt_status & SMRT_CTLR_ASYNC_INTERVENTION) { smrt->smrt_stats.smrts_events_intervened++; if (smrt_submit(smrt, smrt->smrt_event_cmd) == 0) { smrt->smrt_status &= ~SMRT_CTLR_ASYNC_INTERVENTION; } } mutex_exit(&smrt->smrt_mutex); } int smrt_retrieve(smrt_t *smrt) { VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); switch (smrt->smrt_ctlr_mode) { case SMRT_CTLR_MODE_SIMPLE: smrt_retrieve_simple(smrt); return (DDI_SUCCESS); case SMRT_CTLR_MODE_UNKNOWN: break; } panic("unknown controller mode"); /* LINTED: E_FUNC_NO_RET_VAL */ } /* * Grab a new tag number for this command. We aim to avoid reusing tag numbers * as much as possible, so as to avoid spurious double completion from the * controller. */ static void smrt_set_new_tag(smrt_t *smrt, smrt_command_t *smcm) { VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); /* * Loop until we find a tag that is not in use. The tag space is * very large (~30 bits) and the maximum number of inflight commands * is comparatively small (~1024 in current controllers). */ for (;;) { uint32_t new_tag = smrt->smrt_next_tag; if (++smrt->smrt_next_tag > SMRT_MAX_TAG_NUMBER) { smrt->smrt_next_tag = SMRT_MIN_TAG_NUMBER; } if (smrt_lookup_inflight(smrt, new_tag) != NULL) { /* * This tag is already used on an inflight command. * Choose another. */ continue; } /* * Set the tag for the command and also write it into the * appropriate part of the request block. */ smcm->smcm_tag = new_tag; smcm->smcm_va_cmd->Header.Tag.tag_value = new_tag; return; } } /* * Submit a command to the controller. */ int smrt_submit(smrt_t *smrt, smrt_command_t *smcm) { VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); VERIFY(smcm->smcm_type != SMRT_CMDTYPE_PREINIT); /* * Anything that asks us to ignore the running state of the controller * must be wired up to poll for completion. */ if (smcm->smcm_status & SMRT_CMD_IGNORE_RUNNING) { VERIFY(smcm->smcm_status & SMRT_CMD_STATUS_POLLED); } /* * If the controller is currently being reset, do not allow command * submission. However, if this is one of the commands needed to finish * reset, as indicated on the command structure, allow it. */ if (!(smrt->smrt_status & SMRT_CTLR_STATUS_RUNNING) && !(smcm->smcm_status & SMRT_CMD_IGNORE_RUNNING)) { return (EIO); } /* * Do not allow submission of more concurrent commands than the * controller supports. */ if (avl_numnodes(&smrt->smrt_inflight) >= smrt->smrt_maxcmds) { return (EAGAIN); } /* * Synchronise the Command Block DMA resources to ensure that the * device has a consistent view before we pass it the command. */ if (ddi_dma_sync(smcm->smcm_contig.smdma_dma_handle, 0, 0, DDI_DMA_SYNC_FORDEV) != DDI_SUCCESS) { dev_err(smrt->smrt_dip, CE_PANIC, "DMA sync failure"); return (EIO); } /* * Ensure that this command is not re-used without issuing a new * tag number and performing any appropriate cleanup. */ VERIFY(!(smcm->smcm_status & SMRT_CMD_STATUS_USED)); smcm->smcm_status |= SMRT_CMD_STATUS_USED; /* * Assign a tag that is not currently in use */ smrt_set_new_tag(smrt, smcm); /* * Insert this command into the inflight AVL. */ avl_index_t where; if (avl_find(&smrt->smrt_inflight, smcm, &where) != NULL) { dev_err(smrt->smrt_dip, CE_PANIC, "duplicate submit tag %x", smcm->smcm_tag); } avl_insert(&smrt->smrt_inflight, smcm, where); if (smrt->smrt_stats.smrts_max_inflight < avl_numnodes(&smrt->smrt_inflight)) { smrt->smrt_stats.smrts_max_inflight = avl_numnodes(&smrt->smrt_inflight); } VERIFY(!(smcm->smcm_status & SMRT_CMD_STATUS_INFLIGHT)); smcm->smcm_status |= SMRT_CMD_STATUS_INFLIGHT; smcm->smcm_time_submit = gethrtime(); switch (smrt->smrt_ctlr_mode) { case SMRT_CTLR_MODE_SIMPLE: smrt_submit_simple(smrt, smcm); return (0); case SMRT_CTLR_MODE_UNKNOWN: break; } panic("unknown controller mode"); /* LINTED: E_FUNC_NO_RET_VAL */ } static void smrt_process_finishq_sync(smrt_command_t *smcm) { smrt_t *smrt = smcm->smcm_ctlr; if (ddi_dma_sync(smcm->smcm_contig.smdma_dma_handle, 0, 0, DDI_DMA_SYNC_FORCPU) != DDI_SUCCESS) { dev_err(smrt->smrt_dip, CE_PANIC, "finishq DMA sync failure"); } } static void smrt_process_finishq_one(smrt_command_t *smcm) { smrt_t *smrt = smcm->smcm_ctlr; VERIFY(!(smcm->smcm_status & SMRT_CMD_STATUS_COMPLETE)); smcm->smcm_status |= SMRT_CMD_STATUS_COMPLETE; switch (smcm->smcm_type) { case SMRT_CMDTYPE_INTERNAL: cv_broadcast(&smcm->smcm_ctlr->smrt_cv_finishq); return; case SMRT_CMDTYPE_SCSA: smrt_hba_complete(smcm); return; case SMRT_CMDTYPE_EVENT: smrt_event_complete(smcm); return; case SMRT_CMDTYPE_ABORTQ: /* * Abort messages sent as part of abort queue processing * do not require any completion activity. */ mutex_exit(&smrt->smrt_mutex); smrt_command_free(smcm); mutex_enter(&smrt->smrt_mutex); return; case SMRT_CMDTYPE_PREINIT: dev_err(smrt->smrt_dip, CE_PANIC, "preinit command " "completed after initialisation"); return; } panic("unknown command type"); } /* * Process commands in the completion queue. */ void smrt_process_finishq(smrt_t *smrt) { smrt_command_t *smcm; VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); while ((smcm = list_remove_head(&smrt->smrt_finishq)) != NULL) { /* * Synchronise the Command Block before we read from it or * free it, to ensure that any writes from the controller are * visible. */ smrt_process_finishq_sync(smcm); /* * Check if this command was in line to be aborted. */ if (list_link_active(&smcm->smcm_link_abort)) { /* * This command was in line, but the controller * subsequently completed the command before we * were able to do so. */ list_remove(&smrt->smrt_abortq, smcm); smcm->smcm_status &= ~SMRT_CMD_STATUS_TIMEOUT; } /* * Check if this command has been abandoned by the original * submitter. If it has, free it now to avoid a leak. */ if (smcm->smcm_status & SMRT_CMD_STATUS_ABANDONED) { mutex_exit(&smrt->smrt_mutex); smrt_command_free(smcm); mutex_enter(&smrt->smrt_mutex); continue; } if (smcm->smcm_status & SMRT_CMD_STATUS_POLLED) { /* * This command will be picked up and processed * by "smrt_poll_for()" once the CV is triggered * at the end of processing. */ smcm->smcm_status |= SMRT_CMD_STATUS_POLL_COMPLETE; continue; } smrt_process_finishq_one(smcm); } cv_broadcast(&smrt->smrt_cv_finishq); } /* * Process commands in the abort queue. */ void smrt_process_abortq(smrt_t *smrt) { smrt_command_t *smcm; smrt_command_t *abort_smcm = NULL; VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); if (list_is_empty(&smrt->smrt_abortq)) { goto out; } another: mutex_exit(&smrt->smrt_mutex); if ((abort_smcm = smrt_command_alloc(smrt, SMRT_CMDTYPE_ABORTQ, KM_NOSLEEP)) == NULL) { /* * No resources available to send abort messages. We will * try again the next time around. */ mutex_enter(&smrt->smrt_mutex); goto out; } mutex_enter(&smrt->smrt_mutex); while ((smcm = list_remove_head(&smrt->smrt_abortq)) != NULL) { if (!(smcm->smcm_status & SMRT_CMD_STATUS_INFLIGHT)) { /* * This message is not currently inflight, so * no abort is needed. */ continue; } if (smcm->smcm_status & SMRT_CMD_STATUS_ABORT_SENT) { /* * An abort message has already been sent for * this command. */ continue; } /* * Send an abort message for the command. */ smrt_write_message_abort_one(abort_smcm, smcm->smcm_tag); if (smrt_submit(smrt, abort_smcm) != 0) { /* * The command could not be submitted to the * controller. Put it back in the abort queue * and give up for now. */ list_insert_head(&smrt->smrt_abortq, smcm); goto out; } smcm->smcm_status |= SMRT_CMD_STATUS_ABORT_SENT; /* * Record some debugging information about the abort we * sent: */ smcm->smcm_abort_time = gethrtime(); smcm->smcm_abort_tag = abort_smcm->smcm_tag; /* * The abort message was sent. Release it and * allocate another command. */ abort_smcm = NULL; goto another; } out: cv_broadcast(&smrt->smrt_cv_finishq); if (abort_smcm != NULL) { mutex_exit(&smrt->smrt_mutex); smrt_command_free(abort_smcm); mutex_enter(&smrt->smrt_mutex); } } int smrt_poll_for(smrt_t *smrt, smrt_command_t *smcm) { VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); VERIFY(smcm->smcm_status & SMRT_CMD_STATUS_POLLED); while (!(smcm->smcm_status & SMRT_CMD_STATUS_POLL_COMPLETE)) { if (smcm->smcm_expiry != 0) { /* * This command has an expiry time. Check to see * if it has already passed: */ if (smcm->smcm_expiry < gethrtime()) { return (ETIMEDOUT); } } if (ddi_in_panic()) { /* * When the system is panicking, there are no * interrupts or other threads. Drive the polling loop * on our own, but with a small delay to avoid * aggrevating the controller while we're trying to * dump. */ (void) smrt_retrieve(smrt); smrt_process_finishq(smrt); drv_usecwait(100); continue; } /* * Wait for command completion to return through the regular * interrupt handling path. */ if (smcm->smcm_expiry == 0) { cv_wait(&smrt->smrt_cv_finishq, &smrt->smrt_mutex); } else { /* * Wait only until the expiry time for this command. */ (void) cv_timedwait_sig_hrtime(&smrt->smrt_cv_finishq, &smrt->smrt_mutex, smcm->smcm_expiry); } } /* * Fire the completion callback for this command. The callback * is responsible for freeing the command, so it may not be * referenced again once this call returns. */ smrt_process_finishq_one(smcm); return (0); } void smrt_intr_set(smrt_t *smrt, boolean_t enabled) { /* * Read the Interrupt Mask Register. */ uint32_t imr = smrt_get32(smrt, CISS_I2O_INTERRUPT_MASK); switch (smrt->smrt_ctlr_mode) { case SMRT_CTLR_MODE_SIMPLE: if (enabled) { imr &= ~CISS_IMR_BIT_SIMPLE_INTR_DISABLE; } else { imr |= CISS_IMR_BIT_SIMPLE_INTR_DISABLE; } smrt_put32(smrt, CISS_I2O_INTERRUPT_MASK, imr); return; case SMRT_CTLR_MODE_UNKNOWN: break; } panic("unknown controller mode"); } /* * Signal to the controller that we have updated the Configuration Table by * writing to the Inbound Doorbell Register. The controller will, after some * number of seconds, acknowledge this by clearing the bit. * * If successful, return DDI_SUCCESS. If the controller takes too long to * acknowledge, return DDI_FAILURE. */ int smrt_cfgtbl_flush(smrt_t *smrt) { /* * Read the current value of the Inbound Doorbell Register. */ uint32_t idr = smrt_get32(smrt, CISS_I2O_INBOUND_DOORBELL); /* * Signal the Configuration Table change to the controller. */ idr |= CISS_IDR_BIT_CFGTBL_CHANGE; smrt_put32(smrt, CISS_I2O_INBOUND_DOORBELL, idr); /* * Wait for the controller to acknowledge the change. */ for (unsigned i = 0; i < smrt_ciss_init_time; i++) { idr = smrt_get32(smrt, CISS_I2O_INBOUND_DOORBELL); if ((idr & CISS_IDR_BIT_CFGTBL_CHANGE) == 0) { return (DDI_SUCCESS); } /* * Wait for one second before trying again. */ delay(drv_usectohz(1000000)); } dev_err(smrt->smrt_dip, CE_WARN, "time out expired before controller " "configuration completed"); return (DDI_FAILURE); } int smrt_cfgtbl_transport_has_support(smrt_t *smrt, int xport) { VERIFY(xport == CISS_CFGTBL_XPORT_SIMPLE); /* * Read the current value of the "Supported Transport Methods" field in * the Configuration Table. */ uint32_t xport_active = ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->TransportSupport); /* * Check that the desired transport method is supported by the * controller: */ if ((xport_active & xport) == 0) { dev_err(smrt->smrt_dip, CE_WARN, "controller does not support " "method \"%s\"", xport == CISS_CFGTBL_XPORT_SIMPLE ? "simple" : "performant"); return (DDI_FAILURE); } return (DDI_SUCCESS); } void smrt_cfgtbl_transport_set(smrt_t *smrt, int xport) { VERIFY(xport == CISS_CFGTBL_XPORT_SIMPLE); ddi_put32(smrt->smrt_ct_handle, &smrt->smrt_ct->TransportRequest, xport); } int smrt_cfgtbl_transport_confirm(smrt_t *smrt, int xport) { VERIFY(xport == CISS_CFGTBL_XPORT_SIMPLE); /* * Read the current value of the TransportActive field in the * Configuration Table. */ uint32_t xport_active = ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->TransportActive); /* * Check that the desired transport method is now active: */ if ((xport_active & xport) == 0) { dev_err(smrt->smrt_dip, CE_WARN, "failed to enable transport " "method \"%s\"", xport == CISS_CFGTBL_XPORT_SIMPLE ? "simple" : "performant"); return (DDI_FAILURE); } /* * Ensure that the controller is now ready to accept commands. */ if ((xport_active & CISS_CFGTBL_READY_FOR_COMMANDS) == 0) { dev_err(smrt->smrt_dip, CE_WARN, "controller not ready to " "accept commands"); return (DDI_FAILURE); } return (DDI_SUCCESS); } uint32_t smrt_ctlr_get_maxsgelements(smrt_t *smrt) { return (ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->MaxSGElements)); } uint32_t smrt_ctlr_get_cmdsoutmax(smrt_t *smrt) { return (ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->CmdsOutMax)); } static uint32_t smrt_ctlr_get_hostdrvsup(smrt_t *smrt) { return (ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->HostDrvrSupport)); } int smrt_ctlr_init(smrt_t *smrt) { uint8_t signature[4] = { 'C', 'I', 'S', 'S' }; int e; if ((e = smrt_ctlr_wait_for_state(smrt, SMRT_WAIT_STATE_READY)) != DDI_SUCCESS) { return (e); } /* * The configuration table contains an ASCII signature ("CISS") which * should be checked as we initialise the controller. * See: "9.1 Configuration Table" in CISS Specification. */ for (unsigned i = 0; i < 4; i++) { if (ddi_get8(smrt->smrt_ct_handle, &smrt->smrt_ct->Signature[i]) != signature[i]) { dev_err(smrt->smrt_dip, CE_WARN, "invalid signature " "detected"); return (DDI_FAILURE); } } /* * Initialise an appropriate Transport Method. For now, this driver * only supports the "Simple" method. */ if ((e = smrt_ctlr_init_simple(smrt)) != DDI_SUCCESS) { return (e); } /* * Save some common feature support bitfields. */ smrt->smrt_host_support = smrt_ctlr_get_hostdrvsup(smrt); smrt->smrt_bus_support = ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->BusTypes); /* * Read initial controller heartbeat value and mark the current * reading time. */ smrt->smrt_last_heartbeat = ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->HeartBeat); smrt->smrt_last_heartbeat_time = gethrtime(); /* * Determine the firmware version of the controller so that we can * select which type of interrupts to use. */ if ((e = smrt_ctlr_versions(smrt, SMRT_DISCOVER_TIMEOUT, &smrt->smrt_versions)) != 0) { dev_err(smrt->smrt_dip, CE_WARN, "could not identify " "controller (%d)", e); return (DDI_FAILURE); } dev_err(smrt->smrt_dip, CE_NOTE, "!firmware rev %s", smrt->smrt_versions.smrtv_firmware_rev); return (DDI_SUCCESS); } void smrt_ctlr_teardown(smrt_t *smrt) { smrt->smrt_status &= ~SMRT_CTLR_STATUS_RUNNING; switch (smrt->smrt_ctlr_mode) { case SMRT_CTLR_MODE_SIMPLE: smrt_ctlr_teardown_simple(smrt); return; case SMRT_CTLR_MODE_UNKNOWN: return; } panic("unknown controller mode"); } int smrt_ctlr_wait_for_state(smrt_t *smrt, smrt_wait_state_t state) { unsigned wait_usec = 100 * 1000; unsigned wait_count = SMRT_WAIT_DELAY_SECONDS * 1000000 / wait_usec; VERIFY(state == SMRT_WAIT_STATE_READY || state == SMRT_WAIT_STATE_UNREADY); /* * Read from the Scratchpad Register until the expected ready signature * is detected. This behaviour is not described in the CISS * specification. * * If the device is not in the desired state immediately, sleep for a * second and try again. If the device has not become ready in 300 * seconds, give up. */ for (unsigned i = 0; i < wait_count; i++) { uint32_t spr = smrt_get32(smrt, CISS_I2O_SCRATCHPAD); switch (state) { case SMRT_WAIT_STATE_READY: if (spr == CISS_SCRATCHPAD_INITIALISED) { return (DDI_SUCCESS); } break; case SMRT_WAIT_STATE_UNREADY: if (spr != CISS_SCRATCHPAD_INITIALISED) { return (DDI_SUCCESS); } break; } if (ddi_in_panic()) { /* * There is no sleep for the panicking, so we * must spin wait: */ drv_usecwait(wait_usec); } else { /* * Wait for a quarter second and try again. */ delay(drv_usectohz(wait_usec)); } } dev_err(smrt->smrt_dip, CE_WARN, "time out waiting for controller " "to enter state \"%s\"", state == SMRT_WAIT_STATE_READY ? "ready": "unready"); return (DDI_FAILURE); } void smrt_lockup_check(smrt_t *smrt) { /* * Read the current controller heartbeat value. */ uint32_t heartbeat = ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->HeartBeat); VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); /* * Check to see if the value is the same as last time we looked: */ if (heartbeat != smrt->smrt_last_heartbeat) { /* * The heartbeat value has changed, which suggests that the * firmware in the controller has not yet come to a complete * stop. Record the new value, as well as the current time. */ smrt->smrt_last_heartbeat = heartbeat; smrt->smrt_last_heartbeat_time = gethrtime(); return; } /* * The controller _might_ have been able to signal to us that is * has locked up. This is a truly unfathomable state of affairs: * If the firmware can tell it has flown off the rails, why not * simply reset the controller? */ uint32_t odr = smrt_get32(smrt, CISS_I2O_OUTBOUND_DOORBELL_STATUS); uint32_t spr = smrt_get32(smrt, CISS_I2O_SCRATCHPAD); if ((odr & CISS_ODR_BIT_LOCKUP) != 0) { dev_err(smrt->smrt_dip, CE_PANIC, "HP SmartArray firmware has " "reported a critical fault (odr %08x spr %08x)", odr, spr); } if (gethrtime() > smrt->smrt_last_heartbeat_time + 60 * NANOSEC) { dev_err(smrt->smrt_dip, CE_PANIC, "HP SmartArray firmware has " "stopped responding (odr %08x spr %08x)", odr, spr); } } /* * Probe the controller with the IDENTIFY CONTROLLER request. This is a BMIC * command, so it must be submitted to the controller and we must poll for its * completion. This functionality is only presently used during controller * initialisation, so it uses the special pre-initialisation path for command * allocation and submission. */ static int smrt_ctlr_identify(smrt_t *smrt, uint16_t timeout, smrt_identify_controller_t *resp) { smrt_command_t *smcm; smrt_identify_controller_req_t smicr; int r; size_t sz; /* * Allocate a command with a data buffer; the controller will fill it * with identification information. There is some suggestion in the * firmware-level specification that the buffer length should be a * multiple of 512 bytes for some controllers, so we round up. */ sz = P2ROUNDUP_TYPED(sizeof (*resp), 512, size_t); if ((smcm = smrt_command_alloc_preinit(smrt, sz, KM_SLEEP)) == NULL) { return (ENOMEM); } smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN); smcm->smcm_va_cmd->Request.CDBLen = sizeof (smicr); smcm->smcm_va_cmd->Request.Timeout = timeout; smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_CMD; smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_SIMPLE; smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_READ; /* * Construct the IDENTIFY CONTROLLER request CDB. Note that any * reserved fields in the request must be filled with zeroes. */ bzero(&smicr, sizeof (smicr)); smicr.smicr_opcode = CISS_SCMD_BMIC_READ; smicr.smicr_lun = 0; smicr.smicr_command = CISS_BMIC_IDENTIFY_CONTROLLER; bcopy(&smicr, &smcm->smcm_va_cmd->Request.CDB[0], MIN(CISS_CDBLEN, sizeof (smicr))); /* * Send the command to the device and poll for its completion. */ smcm->smcm_status |= SMRT_CMD_STATUS_POLLED; smcm->smcm_expiry = gethrtime() + timeout * NANOSEC; if ((r = smrt_preinit_command_simple(smrt, smcm)) != 0) { VERIFY3S(r, ==, ETIMEDOUT); VERIFY0(smcm->smcm_status & SMRT_CMD_STATUS_POLL_COMPLETE); /* * This command timed out, but the driver is not presently * initialised to the point where we can try to abort it. * The command was created with the PREINIT type, so it * does not appear in the global command tracking list. * In order to avoid problems with DMA from the controller, * we have to leak the command allocation. */ smcm = NULL; goto out; } if (smcm->smcm_status & SMRT_CMD_STATUS_RESET_SENT) { /* * The controller was reset while we were trying to identify * it. Report failure. */ r = EIO; goto out; } if (smcm->smcm_status & SMRT_CMD_STATUS_ERROR) { ErrorInfo_t *ei = smcm->smcm_va_err; if (ei->CommandStatus != CISS_CMD_DATA_UNDERRUN) { dev_err(smrt->smrt_dip, CE_WARN, "identify " "controller error: status 0x%x", ei->CommandStatus); r = EIO; goto out; } } if (resp != NULL) { /* * Copy the identify response out for the caller. */ bcopy(smcm->smcm_internal->smcmi_va, resp, sizeof (*resp)); } r = 0; out: if (smcm != NULL) { smrt_command_free(smcm); } return (r); } /* * The firmware versions in an IDENTIFY CONTROLLER response generally take * the form of a four byte ASCII string containing a dotted decimal version * number; e.g., "8.00". * * This function sanitises the firmware version, replacing unexpected * values with a question mark. */ static void smrt_copy_firmware_version(uint8_t *src, char *dst) { for (unsigned i = 0; i < 4; i++) { /* * Make sure that this is a 7-bit clean ASCII value. */ char c = src[i] <= 0x7f ? (char)(src[i] & 0x7f) : '?'; if (isalnum(c) || c == '.' || c == ' ') { dst[i] = c; } else { dst[i] = '?'; } } dst[4] = '\0'; } /* * Using an IDENTIFY CONTROLLER request, determine firmware and controller * version details. See the comments for "smrt_ctlr_identify()" for more * details about calling context. */ static int smrt_ctlr_versions(smrt_t *smrt, uint16_t timeout, smrt_versions_t *smrtv) { smrt_identify_controller_t smic; int r; if ((r = smrt_ctlr_identify(smrt, timeout, &smic)) != 0) { return (r); } smrtv->smrtv_hardware_version = smic.smic_hardware_version; smrt_copy_firmware_version(smic.smic_firmware_rev, smrtv->smrtv_firmware_rev); smrt_copy_firmware_version(smic.smic_recovery_rev, smrtv->smrtv_recovery_rev); smrt_copy_firmware_version(smic.smic_bootblock_rev, smrtv->smrtv_bootblock_rev); return (0); } int smrt_ctlr_reset(smrt_t *smrt) { smrt_command_t *smcm, *smcm_nop; int r; VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); if (ddi_in_panic()) { goto skip_check; } if (smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) { /* * Don't pile on. One reset is enough. Wait until * it's complete, and then return success. */ while (smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) { cv_wait(&smrt->smrt_cv_finishq, &smrt->smrt_mutex); } return (0); } smrt->smrt_status |= SMRT_CTLR_STATUS_RESETTING; smrt->smrt_last_reset_start = gethrtime(); smrt->smrt_stats.smrts_ctlr_resets++; skip_check: /* * Allocate two commands: one for the soft reset message, which we * cannot free until the controller has reset; and one for the ping we * will use to determine when it is once again functional. */ mutex_exit(&smrt->smrt_mutex); if ((smcm = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL, KM_NOSLEEP)) == NULL) { mutex_enter(&smrt->smrt_mutex); return (ENOMEM); } if ((smcm_nop = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL, KM_NOSLEEP)) == NULL) { smrt_command_free(smcm); mutex_enter(&smrt->smrt_mutex); return (ENOMEM); } mutex_enter(&smrt->smrt_mutex); /* * Send a soft reset command to the controller. If this command * succeeds, there will likely be no completion notification. Instead, * the device should become unavailable for some period of time and * then become available again. Once available again, we know the soft * reset has completed and should abort all in-flight commands. */ smrt_write_message_reset_ctlr(smcm); /* * Disable interrupts now. */ smrt_intr_set(smrt, B_FALSE); dev_err(smrt->smrt_dip, CE_WARN, "attempting controller soft reset"); smcm->smcm_status |= SMRT_CMD_STATUS_POLLED; if ((r = smrt_submit(smrt, smcm)) != 0) { dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: " "submit failed (%d)", r); } /* * Mark every currently inflight command as being reset, including the * soft reset command we just sent. Once we confirm the reset works, * we can safely report that these commands have failed. */ for (smrt_command_t *t = avl_first(&smrt->smrt_inflight); t != NULL; t = AVL_NEXT(&smrt->smrt_inflight, t)) { t->smcm_status |= SMRT_CMD_STATUS_RESET_SENT; } /* * Now that we have submitted our soft reset command, prevent * the rest of the driver from interacting with the controller. */ smrt->smrt_status &= ~SMRT_CTLR_STATUS_RUNNING; /* * We do not expect a completion from the controller for our soft * reset command, but we also cannot remove it from the inflight * list until we know the controller has actually reset. To do * otherwise would potentially allow the controller to scribble * on the memory we were using. */ smcm->smcm_status |= SMRT_CMD_STATUS_ABANDONED; if (smrt_ctlr_wait_for_state(smrt, SMRT_WAIT_STATE_UNREADY) != DDI_SUCCESS) { dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: " "controller did not become unready"); } dev_err(smrt->smrt_dip, CE_NOTE, "soft reset: controller unready"); if (smrt_ctlr_wait_for_state(smrt, SMRT_WAIT_STATE_READY) != DDI_SUCCESS) { dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: " "controller did not come become ready"); } dev_err(smrt->smrt_dip, CE_NOTE, "soft reset: controller ready"); /* * In at least the Smart Array P420i, the controller can take 30-45 * seconds after the scratchpad register shows it as being available * before it is ready to receive commands. In order to avoid hitting * it too early with our post-reset ping, we will sleep for 10 seconds * here. */ if (ddi_in_panic()) { drv_usecwait(10 * MICROSEC); } else { delay(drv_usectohz(10 * MICROSEC)); } smrt_ctlr_teardown(smrt); if (smrt_ctlr_init(smrt) != DDI_SUCCESS) { dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: " "controller transport could not be configured"); } dev_err(smrt->smrt_dip, CE_NOTE, "soft reset: controller configured"); smrt_write_message_nop(smcm_nop, 0); smcm_nop->smcm_status |= SMRT_CMD_STATUS_POLLED | SMRT_CMD_IGNORE_RUNNING; if ((r = smrt_submit(smrt, smcm_nop)) != 0) { dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: " "ping could not be submitted (%d)", r); } /* * Interrupts are still masked at this stage. Poll manually in * a way that will not trigger regular finish queue processing: */ VERIFY(smcm_nop->smcm_status & SMRT_CMD_STATUS_INFLIGHT); for (unsigned i = 0; i < 600; i++) { smrt_retrieve_simple(smrt); if (!(smcm_nop->smcm_status & SMRT_CMD_STATUS_INFLIGHT)) { /* * Remove the ping command from the finish queue and * process it manually. This processing must mirror * what would have been done in smrt_process_finishq(). */ VERIFY(list_link_active(&smcm_nop->smcm_link_finish)); list_remove(&smrt->smrt_finishq, smcm_nop); smrt_process_finishq_sync(smcm_nop); smcm_nop->smcm_status |= SMRT_CMD_STATUS_POLL_COMPLETE; smrt_process_finishq_one(smcm_nop); break; } if (ddi_in_panic()) { drv_usecwait(100 * 1000); } else { delay(drv_usectohz(100 * 1000)); } } if (!(smcm_nop->smcm_status & SMRT_CMD_STATUS_COMPLETE)) { dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: " "ping did not complete"); } else if (smcm_nop->smcm_status & SMRT_CMD_STATUS_ERROR) { dev_err(smrt->smrt_dip, CE_WARN, "soft reset: ping completed " "in error (status %u)", (unsigned)smcm_nop->smcm_va_err->CommandStatus); } else { dev_err(smrt->smrt_dip, CE_NOTE, "soft reset: ping completed"); } /* * Now that the controller is working again, we can abort any * commands that were inflight during the reset. */ smrt_command_t *nt; for (smrt_command_t *t = avl_first(&smrt->smrt_inflight); t != NULL; t = nt) { nt = AVL_NEXT(&smrt->smrt_inflight, t); if (t->smcm_status & SMRT_CMD_STATUS_RESET_SENT) { avl_remove(&smrt->smrt_inflight, t); t->smcm_status &= ~SMRT_CMD_STATUS_INFLIGHT; list_insert_tail(&smrt->smrt_finishq, t); } } /* * Quiesce our discovery thread. Note, because * SMRT_CTLR_STATUS_RESTARTING is set, nothing can cause it to be * enabled again. */ if (!ddi_in_panic()) { mutex_exit(&smrt->smrt_mutex); ddi_taskq_wait(smrt->smrt_discover_taskq); mutex_enter(&smrt->smrt_mutex); } /* * Re-enable interrupts. Now, we must kick off a discovery to make sure * that the system is in a sane state and that we can perform I/O. */ smrt_intr_set(smrt, B_TRUE); smrt->smrt_status &= ~SMRT_CTLR_STATUS_RESETTING; smrt->smrt_status |= SMRT_CTLR_DISCOVERY_REQUIRED; /* * Attempt a discovery to make sure that the drivers sees a realistic * view of the world. If we're not in panic context, spin for the * asynchronous process to complete, otherwise we're in panic context * and this is going to happen regardless if we want it to or not. * Before we kick off the request to run discovery, we reset the * discovery request flags as we know that nothing else can consider * running discovery and we don't want to delay until the next smrt * periodic tick if we can avoid it. In panic context, if this failed, * then we won't make it back. */ VERIFY0(smrt->smrt_status & SMRT_CTLR_DISCOVERY_RUNNING); smrt->smrt_status &= ~(SMRT_CTLR_DISCOVERY_MASK); smrt_discover(smrt); if (!ddi_in_panic()) { while (smrt->smrt_status & SMRT_CTLR_DISCOVERY_REQUIRED) { cv_wait(&smrt->smrt_cv_finishq, &smrt->smrt_mutex); } } smrt->smrt_status |= SMRT_CTLR_STATUS_RUNNING; smrt->smrt_last_reset_finish = gethrtime(); /* * Wake anybody that was waiting for the reset to complete. */ cv_broadcast(&smrt->smrt_cv_finishq); /* * Process the completion queue one last time before we let go * of the mutex. */ smrt_process_finishq(smrt); mutex_exit(&smrt->smrt_mutex); smrt_command_free(smcm_nop); mutex_enter(&smrt->smrt_mutex); return (0); } int smrt_event_init(smrt_t *smrt) { int ret; smrt_command_t *event, *cancel; event = smrt_command_alloc(smrt, SMRT_CMDTYPE_EVENT, KM_NOSLEEP); if (event == NULL) return (ENOMEM); if (smrt_command_attach_internal(smrt, event, SMRT_EVENT_NOTIFY_BUFLEN, KM_NOSLEEP) != 0) { smrt_command_free(event); return (ENOMEM); } smrt_write_message_event_notify(event); cancel = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL, KM_NOSLEEP); if (cancel == NULL) { smrt_command_free(event); return (ENOMEM); } if (smrt_command_attach_internal(smrt, cancel, SMRT_EVENT_NOTIFY_BUFLEN, KM_NOSLEEP) != 0) { smrt_command_free(event); smrt_command_free(cancel); return (ENOMEM); } smrt_write_message_cancel_event_notify(cancel); cv_init(&smrt->smrt_event_queue, NULL, CV_DRIVER, NULL); mutex_enter(&smrt->smrt_mutex); if ((ret = smrt_submit(smrt, event)) != 0) { mutex_exit(&smrt->smrt_mutex); smrt_command_free(event); smrt_command_free(cancel); return (ret); } smrt->smrt_event_cmd = event; smrt->smrt_event_cancel_cmd = cancel; mutex_exit(&smrt->smrt_mutex); return (0); } void smrt_event_complete(smrt_command_t *smcm) { smrt_event_notify_t *sen; boolean_t log, rescan; boolean_t intervene = B_FALSE; smrt_t *smrt = smcm->smcm_ctlr; VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); VERIFY3P(smcm, ==, smrt->smrt_event_cmd); VERIFY0(smrt->smrt_status & SMRT_CTLR_ASYNC_INTERVENTION); smrt->smrt_stats.smrts_events_received++; if (smrt->smrt_status & SMRT_CTLR_STATUS_DETACHING) { cv_signal(&smrt->smrt_event_queue); return; } if (smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) { intervene = B_TRUE; goto clean; } /* * The event notification command failed for some reason. Attempt to * drive on and try again at the next intervention period. Because this * may represent a programmer error (though it's hard to know), we wait * until the next intervention period and don't panic. */ if (smcm->smcm_status & SMRT_CMD_STATUS_ERROR) { ErrorInfo_t *ei = smcm->smcm_va_err; intervene = B_TRUE; smrt->smrt_stats.smrts_events_errors++; dev_err(smrt->smrt_dip, CE_WARN, "!event notification request " "error: status 0x%x", ei->CommandStatus); goto clean; } sen = smcm->smcm_internal->smcmi_va; log = rescan = B_FALSE; switch (sen->sen_class) { case SMRT_EVENT_CLASS_PROTOCOL: /* * Most of the event protocol class events aren't really * actionable. However, subclass 1 indicates errors. Today, * the only error is an event overflow. If there's an event * overflow, then we must assume that we need to rescan. */ if (sen->sen_subclass == SMRT_EVENT_PROTOCOL_SUBCLASS_ERROR) { rescan = B_TRUE; } break; case SMRT_EVENT_CLASS_HOTPLUG: /* * We want to log all hotplug events. However we only need to * scan these if the subclass indicates the event is for a disk. */ log = B_TRUE; if (sen->sen_subclass == SMRT_EVENT_HOTPLUG_SUBCLASS_DRIVE) { rescan = B_TRUE; } break; case SMRT_EVENT_CLASS_HWERROR: case SMRT_EVENT_CLASS_ENVIRONMENT: log = B_TRUE; break; case SMRT_EVENT_CLASS_PHYS: log = B_TRUE; /* * This subclass indicates some change for physical drives. As * such, this should trigger a rescan. */ if (sen->sen_subclass == SMRT_EVENT_PHYS_SUBCLASS_STATE) { rescan = B_TRUE; } break; case SMRT_EVENT_CLASS_LOGVOL: rescan = B_TRUE; log = B_TRUE; break; default: /* * While there are other classes of events, it's hard to say how * actionable they are for the moment. If we revamp this such * that it becomes an ireport based system, then we should just * always log these. We opt not to at the moment to try and be * kind to the system log. */ break; } /* * Ideally, this would be an ireport that we could pass onto * administrators; however, since we don't have any way to generate * that, we provide a subset of the event information. */ if (log) { const char *rmsg; if (rescan == B_TRUE) { rmsg = "rescanning"; } else { rmsg = "not rescanning"; } if (sen->sen_message[0] != '\0') { sen->sen_message[sizeof (sen->sen_message) - 1] = '\0'; dev_err(smrt->smrt_dip, CE_NOTE, "!controller event " "class/sub-class/detail %x, %x, %x: %s; %s devices", sen->sen_class, sen->sen_subclass, sen->sen_detail, sen->sen_message, rmsg); } else { dev_err(smrt->smrt_dip, CE_NOTE, "!controller event " "class/sub-class/detail %x, %x, %x; %s devices", sen->sen_class, sen->sen_subclass, sen->sen_detail, rmsg); } } if (rescan) smrt_discover_request(smrt); clean: mutex_exit(&smrt->smrt_mutex); smrt_command_reuse(smcm); bzero(smcm->smcm_internal->smcmi_va, SMRT_EVENT_NOTIFY_BUFLEN); mutex_enter(&smrt->smrt_mutex); /* * Make sure we're not _now_ detaching or resetting. */ if (smrt->smrt_status & SMRT_CTLR_STATUS_DETACHING) { cv_signal(&smrt->smrt_event_queue); return; } if ((smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) != 0 || intervene == B_TRUE) { smrt->smrt_status |= SMRT_CTLR_ASYNC_INTERVENTION; return; } /* * Check out command count per tick. If it's too high, leave it for * intervention to solve. Likely there is some serious driver or * firmware error going on. */ smrt->smrt_event_count++; if (smrt->smrt_event_count > smrt_event_intervention_threshold) { smrt->smrt_status |= SMRT_CTLR_ASYNC_INTERVENTION; return; } if (smrt_submit(smrt, smcm) != 0) { smrt->smrt_status |= SMRT_CTLR_ASYNC_INTERVENTION; } } void smrt_event_fini(smrt_t *smrt) { int ret; smrt_command_t *event, *cancel; mutex_enter(&smrt->smrt_mutex); /* * If intervention has been requested, there is nothing for us to do. We * clear the flag so nothing else accidentally sees this and takes * action. We also don't need to bother sending a cancellation request, * as there is no outstanding event. */ if (smrt->smrt_status & SMRT_CTLR_ASYNC_INTERVENTION) { smrt->smrt_status &= ~SMRT_CTLR_ASYNC_INTERVENTION; goto free; } /* * Submit a cancel request for the event notification queue. Because we * submit both the cancel event and the regular notification event as an * ordered command, we know that by the time this completes, that the * existing one will have completed. */ smrt->smrt_event_cancel_cmd->smcm_status |= SMRT_CMD_STATUS_POLLED; if ((ret = smrt_submit(smrt, smrt->smrt_event_cancel_cmd)) != 0) { /* * This is unfortunate. We've failed to submit the command. At * this point all we can do is reset the device. If the reset * succeeds, we're done and we can clear all the memory. If it * fails, then all we can do is just leak the command and scream * to the system, sorry. */ if (smrt_ctlr_reset(smrt) != 0) { dev_err(smrt->smrt_dip, CE_WARN, "failed to reset " "device after failure to submit cancellation " "(%d), abandoning smrt_command_t at address %p", ret, smrt->smrt_event_cmd); smrt->smrt_event_cmd = NULL; goto free; } } smrt->smrt_event_cancel_cmd->smcm_expiry = gethrtime() + SMRT_ASYNC_CANCEL_TIMEOUT * NANOSEC; if ((ret = smrt_poll_for(smrt, smrt->smrt_event_cancel_cmd)) != 0) { VERIFY3S(ret, ==, ETIMEDOUT); VERIFY0(smrt->smrt_event_cancel_cmd->smcm_status & SMRT_CMD_STATUS_POLL_COMPLETE); /* * The command timed out. All we can do is hope a reset will * work. */ if (smrt_ctlr_reset(smrt) != 0) { dev_err(smrt->smrt_dip, CE_WARN, "failed to reset " "device after failure to poll for async " "cancellation command abandoning smrt_command_t " "event command at address %p and cancellation " "command at %p", smrt->smrt_event_cmd, smrt->smrt_event_cancel_cmd); smrt->smrt_event_cmd = NULL; smrt->smrt_event_cancel_cmd = NULL; goto free; } } /* * Well, in the end, it's results that count. */ if (smrt->smrt_event_cancel_cmd->smcm_status & SMRT_CMD_STATUS_RESET_SENT) { goto free; } if (smrt->smrt_event_cancel_cmd->smcm_status & SMRT_CMD_STATUS_ERROR) { ErrorInfo_t *ei = smrt->smrt_event_cancel_cmd->smcm_va_err; /* * This can return a CISS_CMD_TARGET_STATUS entry when the * controller doesn't think a command is outstanding. It is * possible we raced, so don't think too much about that case. * Anything else leaves us between a rock and a hard place, the * only way out is a reset. */ if (ei->CommandStatus != CISS_CMD_TARGET_STATUS && smrt_ctlr_reset(smrt) != 0) { dev_err(smrt->smrt_dip, CE_WARN, "failed to reset " "device after receiving an error on the async " "cancellation command (%d); abandoning " "smrt_command_t event command at address %p and " "cancellation command at %p", ei->CommandStatus, smrt->smrt_event_cmd, smrt->smrt_event_cancel_cmd); smrt->smrt_event_cmd = NULL; smrt->smrt_event_cancel_cmd = NULL; goto free; } } free: event = smrt->smrt_event_cmd; smrt->smrt_event_cmd = NULL; cancel = smrt->smrt_event_cancel_cmd; smrt->smrt_event_cancel_cmd = NULL; mutex_exit(&smrt->smrt_mutex); if (event != NULL) smrt_command_free(event); if (cancel != NULL) smrt_command_free(cancel); cv_destroy(&smrt->smrt_event_queue); } /* * We've been asked to do a discovery in panic context. This would have * occurred because there was a device reset. Because we can't rely on the * target maps, all we can do at the moment is go over all the active targets * and note which ones no longer exist. If this target was required to dump, * then the dump code will encounter a fatal error. If not, then we should * count ourselves surprisingly lucky. */ static void smrt_discover_panic_check(smrt_t *smrt) { smrt_target_t *smtg; ASSERT(MUTEX_HELD(&smrt->smrt_mutex)); for (smtg = list_head(&smrt->smrt_targets); smtg != NULL; smtg = list_next(&smrt->smrt_targets, smtg)) { uint64_t gen; if (smtg->smtg_physical) { smrt_physical_t *smpt = smtg->smtg_lun.smtg_phys; /* * Don't worry about drives that aren't visible. */ if (!smpt->smpt_visible) continue; gen = smpt->smpt_gen; } else { smrt_volume_t *smlv = smtg->smtg_lun.smtg_vol; gen = smlv->smlv_gen; } if (gen != smrt->smrt_discover_gen) { dev_err(smrt->smrt_dip, CE_WARN, "target %s " "disappeared during post-panic discovery", scsi_device_unit_address(smtg->smtg_scsi_dev)); smtg->smtg_gone = B_TRUE; } } } static void smrt_discover(void *arg) { int log = 0, phys = 0; smrt_t *smrt = arg; uint64_t gen; boolean_t runphys, runvirt; mutex_enter(&smrt->smrt_mutex); smrt->smrt_status |= SMRT_CTLR_DISCOVERY_RUNNING; smrt->smrt_status &= ~SMRT_CTLR_DISCOVERY_REQUESTED; smrt->smrt_discover_gen++; gen = smrt->smrt_discover_gen; runphys = smrt->smrt_phys_tgtmap != NULL; runvirt = smrt->smrt_virt_tgtmap != NULL; mutex_exit(&smrt->smrt_mutex); if (runphys) phys = smrt_phys_discover(smrt, SMRT_DISCOVER_TIMEOUT, gen); if (runvirt) log = smrt_logvol_discover(smrt, SMRT_DISCOVER_TIMEOUT, gen); mutex_enter(&smrt->smrt_mutex); if (phys != 0 || log != 0) { if (!ddi_in_panic()) { smrt->smrt_status |= SMRT_CTLR_DISCOVERY_PERIODIC; } else { panic("smrt_t %p failed to perform discovery after " "a reset in panic context, unable to continue. " "logvol: %d, phys: %d", smrt, log, phys); } } else { if (!ddi_in_panic() && smrt->smrt_status & SMRT_CTLR_DISCOVERY_REQUIRED) { smrt->smrt_status &= ~SMRT_CTLR_DISCOVERY_REQUIRED; cv_broadcast(&smrt->smrt_cv_finishq); } if (ddi_in_panic()) { smrt_discover_panic_check(smrt); } } smrt->smrt_status &= ~SMRT_CTLR_DISCOVERY_RUNNING; if (smrt->smrt_status & SMRT_CTLR_DISCOVERY_REQUESTED) smrt->smrt_status |= SMRT_CTLR_DISCOVERY_PERIODIC; mutex_exit(&smrt->smrt_mutex); } /* * Request discovery, which is always run via a taskq. */ void smrt_discover_request(smrt_t *smrt) { boolean_t run; ASSERT(MUTEX_HELD(&smrt->smrt_mutex)); if (ddi_in_panic()) { smrt_discover(smrt); return; } run = (smrt->smrt_status & SMRT_CTLR_DISCOVERY_MASK) == 0; smrt->smrt_status |= SMRT_CTLR_DISCOVERY_REQUESTED; if (run && ddi_taskq_dispatch(smrt->smrt_discover_taskq, smrt_discover, smrt, DDI_NOSLEEP) != DDI_SUCCESS) { smrt->smrt_status |= SMRT_CTLR_DISCOVERY_PERIODIC; smrt->smrt_stats.smrts_discovery_tq_errors++; } }