summaryrefslogtreecommitdiff
path: root/sys/dev/mpr/mpr_sas.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/mpr/mpr_sas.c')
-rw-r--r--sys/dev/mpr/mpr_sas.c3485
1 files changed, 3485 insertions, 0 deletions
diff --git a/sys/dev/mpr/mpr_sas.c b/sys/dev/mpr/mpr_sas.c
new file mode 100644
index 000000000000..1f95358c877e
--- /dev/null
+++ b/sys/dev/mpr/mpr_sas.c
@@ -0,0 +1,3485 @@
+/*-
+ * Copyright (c) 2009 Yahoo! Inc.
+ * Copyright (c) 2011-2014 LSI Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/* Communications core for LSI MPT2 */
+
+/* TODO Move headers to mprvar */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/selinfo.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/bio.h>
+#include <sys/malloc.h>
+#include <sys/uio.h>
+#include <sys/sysctl.h>
+#include <sys/endian.h>
+#include <sys/queue.h>
+#include <sys/kthread.h>
+#include <sys/taskqueue.h>
+#include <sys/sbuf.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <sys/rman.h>
+
+#include <machine/stdarg.h>
+
+#include <cam/cam.h>
+#include <cam/cam_ccb.h>
+#include <cam/cam_debug.h>
+#include <cam/cam_sim.h>
+#include <cam/cam_xpt_sim.h>
+#include <cam/cam_xpt_periph.h>
+#include <cam/cam_periph.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_message.h>
+#if __FreeBSD_version >= 900026
+#include <cam/scsi/smp_all.h>
+#endif
+
+#include <dev/mpr/mpi/mpi2_type.h>
+#include <dev/mpr/mpi/mpi2.h>
+#include <dev/mpr/mpi/mpi2_ioc.h>
+#include <dev/mpr/mpi/mpi2_sas.h>
+#include <dev/mpr/mpi/mpi2_cnfg.h>
+#include <dev/mpr/mpi/mpi2_init.h>
+#include <dev/mpr/mpi/mpi2_tool.h>
+#include <dev/mpr/mpr_ioctl.h>
+#include <dev/mpr/mprvar.h>
+#include <dev/mpr/mpr_table.h>
+#include <dev/mpr/mpr_sas.h>
+
+#define MPRSAS_DISCOVERY_TIMEOUT 20
+#define MPRSAS_MAX_DISCOVERY_TIMEOUTS 10 /* 200 seconds */
+
+/*
+ * static array to check SCSI OpCode for EEDP protection bits
+ */
+#define PRO_R MPI2_SCSIIO_EEDPFLAGS_CHECK_REMOVE_OP
+#define PRO_W MPI2_SCSIIO_EEDPFLAGS_INSERT_OP
+#define PRO_V MPI2_SCSIIO_EEDPFLAGS_INSERT_OP
+static uint8_t op_code_prot[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, PRO_W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V,
+ 0, 0, 0, PRO_W, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, PRO_R, 0, PRO_W, 0, 0, 0, PRO_W, PRO_V,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+MALLOC_DEFINE(M_MPRSAS, "MPRSAS", "MPR SAS memory");
+
+static void mprsas_remove_device(struct mpr_softc *, struct mpr_command *);
+static void mprsas_remove_complete(struct mpr_softc *, struct mpr_command *);
+static void mprsas_action(struct cam_sim *sim, union ccb *ccb);
+static void mprsas_poll(struct cam_sim *sim);
+static void mprsas_scsiio_timeout(void *data);
+static void mprsas_abort_complete(struct mpr_softc *sc,
+ struct mpr_command *cm);
+static void mprsas_action_scsiio(struct mprsas_softc *, union ccb *);
+static void mprsas_scsiio_complete(struct mpr_softc *, struct mpr_command *);
+static void mprsas_action_resetdev(struct mprsas_softc *, union ccb *);
+static void mprsas_resetdev_complete(struct mpr_softc *,
+ struct mpr_command *);
+static int mprsas_send_abort(struct mpr_softc *sc, struct mpr_command *tm,
+ struct mpr_command *cm);
+static int mprsas_send_reset(struct mpr_softc *sc, struct mpr_command *tm,
+ uint8_t type);
+static void mprsas_async(void *callback_arg, uint32_t code,
+ struct cam_path *path, void *arg);
+static void mprsas_prepare_ssu(struct mpr_softc *sc, struct cam_path *path,
+ struct ccb_getdev *cgd);
+#if (__FreeBSD_version < 901503) || \
+ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006))
+static void mprsas_check_eedp(struct mpr_softc *sc, struct cam_path *path,
+ struct ccb_getdev *cgd);
+static void mprsas_read_cap_done(struct cam_periph *periph,
+ union ccb *done_ccb);
+#endif
+static int mprsas_send_portenable(struct mpr_softc *sc);
+static void mprsas_portenable_complete(struct mpr_softc *sc,
+ struct mpr_command *cm);
+
+#if __FreeBSD_version >= 900026
+static void
+mprsas_smpio_complete(struct mpr_softc *sc, struct mpr_command *cm);
+static void mprsas_send_smpcmd(struct mprsas_softc *sassc,
+ union ccb *ccb, uint64_t sasaddr);
+static void
+mprsas_action_smpio(struct mprsas_softc *sassc, union ccb *ccb);
+#endif
+
+struct mprsas_target *
+mprsas_find_target_by_handle(struct mprsas_softc *sassc, int start,
+ uint16_t handle)
+{
+ struct mprsas_target *target;
+ int i;
+
+ for (i = start; i < sassc->maxtargets; i++) {
+ target = &sassc->targets[i];
+ if (target->handle == handle)
+ return (target);
+ }
+
+ return (NULL);
+}
+
+/* we need to freeze the simq during attach and diag reset, to avoid failing
+ * commands before device handles have been found by discovery. Since
+ * discovery involves reading config pages and possibly sending commands,
+ * discovery actions may continue even after we receive the end of discovery
+ * event, so refcount discovery actions instead of assuming we can unfreeze
+ * the simq when we get the event.
+ */
+void
+mprsas_startup_increment(struct mprsas_softc *sassc)
+{
+ MPR_FUNCTRACE(sassc->sc);
+
+ if ((sassc->flags & MPRSAS_IN_STARTUP) != 0) {
+ if (sassc->startup_refcount++ == 0) {
+ /* just starting, freeze the simq */
+ mpr_dprint(sassc->sc, MPR_INIT,
+ "%s freezing simq\n", __func__);
+#if (__FreeBSD_version >= 1000039) || \
+ ((__FreeBSD_version < 1000000) && (__FreeBSD_version >= 902502))
+ xpt_hold_boot();
+#endif
+ xpt_freeze_simq(sassc->sim, 1);
+ }
+ mpr_dprint(sassc->sc, MPR_INIT, "%s refcount %u\n", __func__,
+ sassc->startup_refcount);
+ }
+}
+
+void
+mprsas_release_simq_reinit(struct mprsas_softc *sassc)
+{
+ if (sassc->flags & MPRSAS_QUEUE_FROZEN) {
+ sassc->flags &= ~MPRSAS_QUEUE_FROZEN;
+ xpt_release_simq(sassc->sim, 1);
+ mpr_dprint(sassc->sc, MPR_INFO, "Unfreezing SIM queue\n");
+ }
+}
+
+void
+mprsas_startup_decrement(struct mprsas_softc *sassc)
+{
+ MPR_FUNCTRACE(sassc->sc);
+
+ if ((sassc->flags & MPRSAS_IN_STARTUP) != 0) {
+ if (--sassc->startup_refcount == 0) {
+ /* finished all discovery-related actions, release
+ * the simq and rescan for the latest topology.
+ */
+ mpr_dprint(sassc->sc, MPR_INIT,
+ "%s releasing simq\n", __func__);
+ sassc->flags &= ~MPRSAS_IN_STARTUP;
+ xpt_release_simq(sassc->sim, 1);
+#if (__FreeBSD_version >= 1000039) || \
+ ((__FreeBSD_version < 1000000) && (__FreeBSD_version >= 902502))
+ xpt_release_boot();
+#else
+ mprsas_rescan_target(sassc->sc, NULL);
+#endif
+ }
+ mpr_dprint(sassc->sc, MPR_INIT, "%s refcount %u\n", __func__,
+ sassc->startup_refcount);
+ }
+}
+
+/* LSI's firmware requires us to stop sending commands when we're doing task
+ * management, so refcount the TMs and keep the simq frozen when any are in
+ * use.
+ */
+struct mpr_command *
+mprsas_alloc_tm(struct mpr_softc *sc)
+{
+ struct mpr_command *tm;
+
+ MPR_FUNCTRACE(sc);
+ tm = mpr_alloc_high_priority_command(sc);
+ if (tm != NULL) {
+ if (sc->sassc->tm_count++ == 0) {
+ mpr_dprint(sc, MPR_RECOVERY,
+ "%s freezing simq\n", __func__);
+ xpt_freeze_simq(sc->sassc->sim, 1);
+ }
+ mpr_dprint(sc, MPR_RECOVERY, "%s tm_count %u\n", __func__,
+ sc->sassc->tm_count);
+ }
+ return tm;
+}
+
+void
+mprsas_free_tm(struct mpr_softc *sc, struct mpr_command *tm)
+{
+ mpr_dprint(sc, MPR_TRACE, "%s", __func__);
+ if (tm == NULL)
+ return;
+
+ /* if there are no TMs in use, we can release the simq. We use our
+ * own refcount so that it's easier for a diag reset to cleanup and
+ * release the simq.
+ */
+ if (--sc->sassc->tm_count == 0) {
+ mpr_dprint(sc, MPR_RECOVERY, "%s releasing simq\n", __func__);
+ xpt_release_simq(sc->sassc->sim, 1);
+ }
+ mpr_dprint(sc, MPR_RECOVERY, "%s tm_count %u\n", __func__,
+ sc->sassc->tm_count);
+
+ mpr_free_high_priority_command(sc, tm);
+}
+
+void
+mprsas_rescan_target(struct mpr_softc *sc, struct mprsas_target *targ)
+{
+ struct mprsas_softc *sassc = sc->sassc;
+ path_id_t pathid;
+ target_id_t targetid;
+ union ccb *ccb;
+
+ MPR_FUNCTRACE(sc);
+ pathid = cam_sim_path(sassc->sim);
+ if (targ == NULL)
+ targetid = CAM_TARGET_WILDCARD;
+ else
+ targetid = targ - sassc->targets;
+
+ /*
+ * Allocate a CCB and schedule a rescan.
+ */
+ ccb = xpt_alloc_ccb_nowait();
+ if (ccb == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "unable to alloc CCB for rescan\n");
+ return;
+ }
+
+ if (xpt_create_path(&ccb->ccb_h.path, NULL, pathid,
+ targetid, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
+ mpr_dprint(sc, MPR_ERROR, "unable to create path for rescan\n");
+ xpt_free_ccb(ccb);
+ return;
+ }
+
+ if (targetid == CAM_TARGET_WILDCARD)
+ ccb->ccb_h.func_code = XPT_SCAN_BUS;
+ else
+ ccb->ccb_h.func_code = XPT_SCAN_TGT;
+
+ mpr_dprint(sc, MPR_TRACE, "%s targetid %u\n", __func__, targetid);
+ xpt_rescan(ccb);
+}
+
+static void
+mprsas_log_command(struct mpr_command *cm, u_int level, const char *fmt, ...)
+{
+ struct sbuf sb;
+ va_list ap;
+ char str[192];
+ char path_str[64];
+
+ if (cm == NULL)
+ return;
+
+ /* No need to be in here if debugging isn't enabled */
+ if ((cm->cm_sc->mpr_debug & level) == 0)
+ return;
+
+ sbuf_new(&sb, str, sizeof(str), 0);
+
+ va_start(ap, fmt);
+
+ if (cm->cm_ccb != NULL) {
+ xpt_path_string(cm->cm_ccb->csio.ccb_h.path, path_str,
+ sizeof(path_str));
+ sbuf_cat(&sb, path_str);
+ if (cm->cm_ccb->ccb_h.func_code == XPT_SCSI_IO) {
+ scsi_command_string(&cm->cm_ccb->csio, &sb);
+ sbuf_printf(&sb, "length %d ",
+ cm->cm_ccb->csio.dxfer_len);
+ }
+ } else {
+ sbuf_printf(&sb, "(noperiph:%s%d:%u:%u:%u): ",
+ cam_sim_name(cm->cm_sc->sassc->sim),
+ cam_sim_unit(cm->cm_sc->sassc->sim),
+ cam_sim_bus(cm->cm_sc->sassc->sim),
+ cm->cm_targ ? cm->cm_targ->tid : 0xFFFFFFFF,
+ cm->cm_lun);
+ }
+
+ sbuf_printf(&sb, "SMID %u ", cm->cm_desc.Default.SMID);
+ sbuf_vprintf(&sb, fmt, ap);
+ sbuf_finish(&sb);
+ mpr_dprint_field(cm->cm_sc, level, "%s", sbuf_data(&sb));
+
+ va_end(ap);
+}
+
+static void
+mprsas_remove_volume(struct mpr_softc *sc, struct mpr_command *tm)
+{
+ MPI2_SCSI_TASK_MANAGE_REPLY *reply;
+ struct mprsas_target *targ;
+ uint16_t handle;
+
+ MPR_FUNCTRACE(sc);
+
+ reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply;
+ handle = (uint16_t)(uintptr_t)tm->cm_complete_data;
+ targ = tm->cm_targ;
+
+ if (reply == NULL) {
+ /* XXX retry the remove after the diag reset completes? */
+ mpr_dprint(sc, MPR_FAULT, "%s NULL reply resetting device "
+ "0x%04x\n", __func__, handle);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ if (reply->IOCStatus != MPI2_IOCSTATUS_SUCCESS) {
+ mpr_dprint(sc, MPR_FAULT, "IOCStatus = 0x%x while resetting "
+ "device 0x%x\n", reply->IOCStatus, handle);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ mpr_dprint(sc, MPR_XINFO, "Reset aborted %u commands\n",
+ reply->TerminationCount);
+ mpr_free_reply(sc, tm->cm_reply_data);
+ tm->cm_reply = NULL; /* Ensures the reply won't get re-freed */
+
+ mpr_dprint(sc, MPR_XINFO, "clearing target %u handle 0x%04x\n",
+ targ->tid, handle);
+
+ /*
+ * Don't clear target if remove fails because things will get confusing.
+ * Leave the devname and sasaddr intact so that we know to avoid reusing
+ * this target id if possible, and so we can assign the same target id
+ * to this device if it comes back in the future.
+ */
+ if (reply->IOCStatus == MPI2_IOCSTATUS_SUCCESS) {
+ targ = tm->cm_targ;
+ targ->handle = 0x0;
+ targ->encl_handle = 0x0;
+ targ->encl_level_valid = 0x0;
+ targ->encl_level = 0x0;
+ targ->connector_name[0] = ' ';
+ targ->connector_name[1] = ' ';
+ targ->connector_name[2] = ' ';
+ targ->connector_name[3] = ' ';
+ targ->encl_slot = 0x0;
+ targ->exp_dev_handle = 0x0;
+ targ->phy_num = 0x0;
+ targ->linkrate = 0x0;
+ targ->devinfo = 0x0;
+ targ->flags = 0x0;
+ targ->scsi_req_desc_type = 0;
+ }
+
+ mprsas_free_tm(sc, tm);
+}
+
+
+/*
+ * No Need to call "MPI2_SAS_OP_REMOVE_DEVICE" For Volume removal.
+ * Otherwise Volume Delete is same as Bare Drive Removal.
+ */
+void
+mprsas_prepare_volume_remove(struct mprsas_softc *sassc, uint16_t handle)
+{
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+ struct mpr_softc *sc;
+ struct mpr_command *cm;
+ struct mprsas_target *targ = NULL;
+
+ MPR_FUNCTRACE(sassc->sc);
+ sc = sassc->sc;
+
+ targ = mprsas_find_target_by_handle(sassc, 0, handle);
+ if (targ == NULL) {
+ /* FIXME: what is the action? */
+ /* We don't know about this device? */
+ mpr_dprint(sc, MPR_ERROR,
+ "%s %d : invalid handle 0x%x \n", __func__,__LINE__, handle);
+ return;
+ }
+
+ targ->flags |= MPRSAS_TARGET_INREMOVAL;
+
+ cm = mprsas_alloc_tm(sc);
+ if (cm == NULL) {
+ mpr_dprint(sc, MPR_ERROR,
+ "%s: command alloc failure\n", __func__);
+ return;
+ }
+
+ mprsas_rescan_target(sc, targ);
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)cm->cm_req;
+ req->DevHandle = targ->handle;
+ req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT;
+ req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET;
+
+ /* SAS Hard Link Reset / SATA Link Reset */
+ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET;
+
+ cm->cm_targ = targ;
+ cm->cm_data = NULL;
+ cm->cm_desc.HighPriority.RequestFlags =
+ MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY;
+ cm->cm_complete = mprsas_remove_volume;
+ cm->cm_complete_data = (void *)(uintptr_t)handle;
+ mpr_map_command(sc, cm);
+}
+
+/*
+ * The MPT2 firmware performs debounce on the link to avoid transient link
+ * errors and false removals. When it does decide that link has been lost
+ * and a device needs to go away, it expects that the host will perform a
+ * target reset and then an op remove. The reset has the side-effect of
+ * aborting any outstanding requests for the device, which is required for
+ * the op-remove to succeed. It's not clear if the host should check for
+ * the device coming back alive after the reset.
+ */
+void
+mprsas_prepare_remove(struct mprsas_softc *sassc, uint16_t handle)
+{
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+ struct mpr_softc *sc;
+ struct mpr_command *cm;
+ struct mprsas_target *targ = NULL;
+
+ MPR_FUNCTRACE(sassc->sc);
+
+ sc = sassc->sc;
+
+ targ = mprsas_find_target_by_handle(sassc, 0, handle);
+ if (targ == NULL) {
+ /* FIXME: what is the action? */
+ /* We don't know about this device? */
+ mpr_dprint(sc, MPR_ERROR, "%s : invalid handle 0x%x \n",
+ __func__, handle);
+ return;
+ }
+
+ targ->flags |= MPRSAS_TARGET_INREMOVAL;
+
+ cm = mprsas_alloc_tm(sc);
+ if (cm == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "%s: command alloc failure\n",
+ __func__);
+ return;
+ }
+
+ mprsas_rescan_target(sc, targ);
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)cm->cm_req;
+ memset(req, 0, sizeof(*req));
+ req->DevHandle = htole16(targ->handle);
+ req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT;
+ req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET;
+
+ /* SAS Hard Link Reset / SATA Link Reset */
+ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET;
+
+ cm->cm_targ = targ;
+ cm->cm_data = NULL;
+ cm->cm_desc.HighPriority.RequestFlags =
+ MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY;
+ cm->cm_complete = mprsas_remove_device;
+ cm->cm_complete_data = (void *)(uintptr_t)handle;
+ mpr_map_command(sc, cm);
+}
+
+static void
+mprsas_remove_device(struct mpr_softc *sc, struct mpr_command *tm)
+{
+ MPI2_SCSI_TASK_MANAGE_REPLY *reply;
+ MPI2_SAS_IOUNIT_CONTROL_REQUEST *req;
+ struct mprsas_target *targ;
+ struct mpr_command *next_cm;
+ uint16_t handle;
+
+ MPR_FUNCTRACE(sc);
+
+ reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply;
+ handle = (uint16_t)(uintptr_t)tm->cm_complete_data;
+ targ = tm->cm_targ;
+
+ /*
+ * Currently there should be no way we can hit this case. It only
+ * happens when we have a failure to allocate chain frames, and
+ * task management commands don't have S/G lists.
+ */
+ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for remove of "
+ "handle %#04x! This should not happen!\n", __func__,
+ tm->cm_flags, handle);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ if (reply == NULL) {
+ /* XXX retry the remove after the diag reset completes? */
+ mpr_dprint(sc, MPR_FAULT, "%s NULL reply resetting device "
+ "0x%04x\n", __func__, handle);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ if (le16toh(reply->IOCStatus) != MPI2_IOCSTATUS_SUCCESS) {
+ mpr_dprint(sc, MPR_FAULT, "IOCStatus = 0x%x while resetting "
+ "device 0x%x\n", le16toh(reply->IOCStatus), handle);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ mpr_dprint(sc, MPR_XINFO, "Reset aborted %u commands\n",
+ le32toh(reply->TerminationCount));
+ mpr_free_reply(sc, tm->cm_reply_data);
+ tm->cm_reply = NULL; /* Ensures the reply won't get re-freed */
+
+ /* Reuse the existing command */
+ req = (MPI2_SAS_IOUNIT_CONTROL_REQUEST *)tm->cm_req;
+ memset(req, 0, sizeof(*req));
+ req->Function = MPI2_FUNCTION_SAS_IO_UNIT_CONTROL;
+ req->Operation = MPI2_SAS_OP_REMOVE_DEVICE;
+ req->DevHandle = htole16(handle);
+ tm->cm_data = NULL;
+ tm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
+ tm->cm_complete = mprsas_remove_complete;
+ tm->cm_complete_data = (void *)(uintptr_t)handle;
+
+ mpr_map_command(sc, tm);
+
+ mpr_dprint(sc, MPR_XINFO, "clearing target %u handle 0x%04x\n",
+ targ->tid, handle);
+ if (targ->encl_level_valid) {
+ mpr_dprint(sc, MPR_XINFO, "At enclosure level %d, slot %d, "
+ "connector name (%4s)\n", targ->encl_level, targ->encl_slot,
+ targ->connector_name);
+ }
+ TAILQ_FOREACH_SAFE(tm, &targ->commands, cm_link, next_cm) {
+ union ccb *ccb;
+
+ mpr_dprint(sc, MPR_XINFO, "Completing missed command %p\n", tm);
+ ccb = tm->cm_complete_data;
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ mprsas_scsiio_complete(sc, tm);
+ }
+}
+
+static void
+mprsas_remove_complete(struct mpr_softc *sc, struct mpr_command *tm)
+{
+ MPI2_SAS_IOUNIT_CONTROL_REPLY *reply;
+ uint16_t handle;
+ struct mprsas_target *targ;
+ struct mprsas_lun *lun;
+
+ MPR_FUNCTRACE(sc);
+
+ reply = (MPI2_SAS_IOUNIT_CONTROL_REPLY *)tm->cm_reply;
+ handle = (uint16_t)(uintptr_t)tm->cm_complete_data;
+
+ /*
+ * Currently there should be no way we can hit this case. It only
+ * happens when we have a failure to allocate chain frames, and
+ * task management commands don't have S/G lists.
+ */
+ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ mpr_dprint(sc, MPR_XINFO, "%s: cm_flags = %#x for remove of "
+ "handle %#04x! This should not happen!\n", __func__,
+ tm->cm_flags, handle);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ if (reply == NULL) {
+ /* most likely a chip reset */
+ mpr_dprint(sc, MPR_FAULT, "%s NULL reply removing device "
+ "0x%04x\n", __func__, handle);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ mpr_dprint(sc, MPR_XINFO, "%s on handle 0x%04x, IOCStatus= 0x%x\n",
+ __func__, handle, le16toh(reply->IOCStatus));
+
+ /*
+ * Don't clear target if remove fails because things will get confusing.
+ * Leave the devname and sasaddr intact so that we know to avoid reusing
+ * this target id if possible, and so we can assign the same target id
+ * to this device if it comes back in the future.
+ */
+ if (le16toh(reply->IOCStatus) == MPI2_IOCSTATUS_SUCCESS) {
+ targ = tm->cm_targ;
+ targ->handle = 0x0;
+ targ->encl_handle = 0x0;
+ targ->encl_level_valid = 0x0;
+ targ->encl_level = 0x0;
+ targ->connector_name[0] = ' ';
+ targ->connector_name[1] = ' ';
+ targ->connector_name[2] = ' ';
+ targ->connector_name[3] = ' ';
+ targ->encl_slot = 0x0;
+ targ->exp_dev_handle = 0x0;
+ targ->phy_num = 0x0;
+ targ->linkrate = 0x0;
+ targ->devinfo = 0x0;
+ targ->flags = 0x0;
+ targ->scsi_req_desc_type = 0;
+
+ while (!SLIST_EMPTY(&targ->luns)) {
+ lun = SLIST_FIRST(&targ->luns);
+ SLIST_REMOVE_HEAD(&targ->luns, lun_link);
+ free(lun, M_MPR);
+ }
+ }
+
+ mprsas_free_tm(sc, tm);
+}
+
+static int
+mprsas_register_events(struct mpr_softc *sc)
+{
+ uint8_t events[16];
+
+ bzero(events, 16);
+ setbit(events, MPI2_EVENT_SAS_DEVICE_STATUS_CHANGE);
+ setbit(events, MPI2_EVENT_SAS_DISCOVERY);
+ setbit(events, MPI2_EVENT_SAS_BROADCAST_PRIMITIVE);
+ setbit(events, MPI2_EVENT_SAS_INIT_DEVICE_STATUS_CHANGE);
+ setbit(events, MPI2_EVENT_SAS_INIT_TABLE_OVERFLOW);
+ setbit(events, MPI2_EVENT_SAS_TOPOLOGY_CHANGE_LIST);
+ setbit(events, MPI2_EVENT_SAS_ENCL_DEVICE_STATUS_CHANGE);
+ setbit(events, MPI2_EVENT_IR_CONFIGURATION_CHANGE_LIST);
+ setbit(events, MPI2_EVENT_IR_VOLUME);
+ setbit(events, MPI2_EVENT_IR_PHYSICAL_DISK);
+ setbit(events, MPI2_EVENT_IR_OPERATION_STATUS);
+ setbit(events, MPI2_EVENT_TEMP_THRESHOLD);
+
+ mpr_register_events(sc, events, mprsas_evt_handler, NULL,
+ &sc->sassc->mprsas_eh);
+
+ return (0);
+}
+
+int
+mpr_attach_sas(struct mpr_softc *sc)
+{
+ struct mprsas_softc *sassc;
+ cam_status status;
+ int unit, error = 0;
+
+ MPR_FUNCTRACE(sc);
+
+ sassc = malloc(sizeof(struct mprsas_softc), M_MPR, M_WAITOK|M_ZERO);
+ if (!sassc) {
+ device_printf(sc->mpr_dev, "Cannot allocate memory %s %d\n",
+ __func__, __LINE__);
+ return (ENOMEM);
+ }
+
+ /*
+ * XXX MaxTargets could change during a reinit. since we don't
+ * resize the targets[] array during such an event, cache the value
+ * of MaxTargets here so that we don't get into trouble later. This
+ * should move into the reinit logic.
+ */
+ sassc->maxtargets = sc->facts->MaxTargets;
+ sassc->targets = malloc(sizeof(struct mprsas_target) *
+ sassc->maxtargets, M_MPR, M_WAITOK|M_ZERO);
+ if (!sassc->targets) {
+ device_printf(sc->mpr_dev, "Cannot allocate memory %s %d\n",
+ __func__, __LINE__);
+ free(sassc, M_MPR);
+ return (ENOMEM);
+ }
+ sc->sassc = sassc;
+ sassc->sc = sc;
+
+ if ((sassc->devq = cam_simq_alloc(sc->num_reqs)) == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "Cannot allocate SIMQ\n");
+ error = ENOMEM;
+ goto out;
+ }
+
+ unit = device_get_unit(sc->mpr_dev);
+ sassc->sim = cam_sim_alloc(mprsas_action, mprsas_poll, "mpr", sassc,
+ unit, &sc->mpr_mtx, sc->num_reqs, sc->num_reqs, sassc->devq);
+ if (sassc->sim == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "Cannot allocate SIM\n");
+ error = EINVAL;
+ goto out;
+ }
+
+ TAILQ_INIT(&sassc->ev_queue);
+
+ /* Initialize taskqueue for Event Handling */
+ TASK_INIT(&sassc->ev_task, 0, mprsas_firmware_event_work, sc);
+ sassc->ev_tq = taskqueue_create("mpr_taskq", M_NOWAIT | M_ZERO,
+ taskqueue_thread_enqueue, &sassc->ev_tq);
+
+ /* Run the task queue with lowest priority */
+ taskqueue_start_threads(&sassc->ev_tq, 1, 255, "%s taskq",
+ device_get_nameunit(sc->mpr_dev));
+
+ mpr_lock(sc);
+
+ /*
+ * XXX There should be a bus for every port on the adapter, but since
+ * we're just going to fake the topology for now, we'll pretend that
+ * everything is just a target on a single bus.
+ */
+ if ((error = xpt_bus_register(sassc->sim, sc->mpr_dev, 0)) != 0) {
+ mpr_dprint(sc, MPR_ERROR, "Error %d registering SCSI bus\n",
+ error);
+ mpr_unlock(sc);
+ goto out;
+ }
+
+ /*
+ * Assume that discovery events will start right away. Freezing
+ *
+ * Hold off boot until discovery is complete.
+ */
+ sassc->flags |= MPRSAS_IN_STARTUP | MPRSAS_IN_DISCOVERY;
+ sc->sassc->startup_refcount = 0;
+ mprsas_startup_increment(sassc);
+
+ callout_init(&sassc->discovery_callout, 1 /*mprafe*/);
+
+ sassc->tm_count = 0;
+
+ /*
+ * Register for async events so we can determine the EEDP
+ * capabilities of devices.
+ */
+ status = xpt_create_path(&sassc->path, /*periph*/NULL,
+ cam_sim_path(sc->sassc->sim), CAM_TARGET_WILDCARD,
+ CAM_LUN_WILDCARD);
+ if (status != CAM_REQ_CMP) {
+ mpr_printf(sc, "Error %#x creating sim path\n", status);
+ sassc->path = NULL;
+ } else {
+ int event;
+
+#if (__FreeBSD_version >= 1000006) || \
+ ((__FreeBSD_version >= 901503) && (__FreeBSD_version < 1000000))
+ event = AC_ADVINFO_CHANGED | AC_FOUND_DEVICE;
+#else
+ event = AC_FOUND_DEVICE;
+#endif
+
+ /*
+ * Prior to the CAM locking improvements, we can't call
+ * xpt_register_async() with a particular path specified.
+ *
+ * If a path isn't specified, xpt_register_async() will
+ * generate a wildcard path and acquire the XPT lock while
+ * it calls xpt_action() to execute the XPT_SASYNC_CB CCB.
+ * It will then drop the XPT lock once that is done.
+ *
+ * If a path is specified for xpt_register_async(), it will
+ * not acquire and drop the XPT lock around the call to
+ * xpt_action(). xpt_action() asserts that the caller
+ * holds the SIM lock, so the SIM lock has to be held when
+ * calling xpt_register_async() when the path is specified.
+ *
+ * But xpt_register_async calls xpt_for_all_devices(),
+ * which calls xptbustraverse(), which will acquire each
+ * SIM lock. When it traverses our particular bus, it will
+ * necessarily acquire the SIM lock, which will lead to a
+ * recursive lock acquisition.
+ *
+ * The CAM locking changes fix this problem by acquiring
+ * the XPT topology lock around bus traversal in
+ * xptbustraverse(), so the caller can hold the SIM lock
+ * and it does not cause a recursive lock acquisition.
+ *
+ * These __FreeBSD_version values are approximate, especially
+ * for stable/10, which is two months later than the actual
+ * change.
+ */
+
+#if (__FreeBSD_version < 1000703) || \
+ ((__FreeBSD_version >= 1100000) && (__FreeBSD_version < 1100002))
+ mpr_unlock(sc);
+ status = xpt_register_async(event, mprsas_async, sc,
+ NULL);
+ mpr_lock(sc);
+#else
+ status = xpt_register_async(event, mprsas_async, sc,
+ sassc->path);
+#endif
+
+ if (status != CAM_REQ_CMP) {
+ mpr_dprint(sc, MPR_ERROR,
+ "Error %#x registering async handler for "
+ "AC_ADVINFO_CHANGED events\n", status);
+ xpt_free_path(sassc->path);
+ sassc->path = NULL;
+ }
+ }
+ if (status != CAM_REQ_CMP) {
+ /*
+ * EEDP use is the exception, not the rule.
+ * Warn the user, but do not fail to attach.
+ */
+ mpr_printf(sc, "EEDP capabilities disabled.\n");
+ }
+
+ mpr_unlock(sc);
+
+ mprsas_register_events(sc);
+out:
+ if (error)
+ mpr_detach_sas(sc);
+ return (error);
+}
+
+int
+mpr_detach_sas(struct mpr_softc *sc)
+{
+ struct mprsas_softc *sassc;
+ struct mprsas_lun *lun, *lun_tmp;
+ struct mprsas_target *targ;
+ int i;
+
+ MPR_FUNCTRACE(sc);
+
+ if (sc->sassc == NULL)
+ return (0);
+
+ sassc = sc->sassc;
+ mpr_deregister_events(sc, sassc->mprsas_eh);
+
+ /*
+ * Drain and free the event handling taskqueue with the lock
+ * unheld so that any parallel processing tasks drain properly
+ * without deadlocking.
+ */
+ if (sassc->ev_tq != NULL)
+ taskqueue_free(sassc->ev_tq);
+
+ /* Make sure CAM doesn't wedge if we had to bail out early. */
+ mpr_lock(sc);
+
+ /* Deregister our async handler */
+ if (sassc->path != NULL) {
+ xpt_register_async(0, mprsas_async, sc, sassc->path);
+ xpt_free_path(sassc->path);
+ sassc->path = NULL;
+ }
+
+ if (sassc->flags & MPRSAS_IN_STARTUP)
+ xpt_release_simq(sassc->sim, 1);
+
+ if (sassc->sim != NULL) {
+ xpt_bus_deregister(cam_sim_path(sassc->sim));
+ cam_sim_free(sassc->sim, FALSE);
+ }
+
+ sassc->flags |= MPRSAS_SHUTDOWN;
+ mpr_unlock(sc);
+
+ if (sassc->devq != NULL)
+ cam_simq_free(sassc->devq);
+
+ for (i = 0; i < sassc->maxtargets; i++) {
+ targ = &sassc->targets[i];
+ SLIST_FOREACH_SAFE(lun, &targ->luns, lun_link, lun_tmp) {
+ free(lun, M_MPR);
+ }
+ }
+ free(sassc->targets, M_MPR);
+ free(sassc, M_MPR);
+ sc->sassc = NULL;
+
+ return (0);
+}
+
+void
+mprsas_discovery_end(struct mprsas_softc *sassc)
+{
+ struct mpr_softc *sc = sassc->sc;
+
+ MPR_FUNCTRACE(sc);
+
+ if (sassc->flags & MPRSAS_DISCOVERY_TIMEOUT_PENDING)
+ callout_stop(&sassc->discovery_callout);
+
+}
+
+static void
+mprsas_action(struct cam_sim *sim, union ccb *ccb)
+{
+ struct mprsas_softc *sassc;
+
+ sassc = cam_sim_softc(sim);
+
+ MPR_FUNCTRACE(sassc->sc);
+ mpr_dprint(sassc->sc, MPR_TRACE, "%s func 0x%x\n", __func__,
+ ccb->ccb_h.func_code);
+ mtx_assert(&sassc->sc->mpr_mtx, MA_OWNED);
+
+ switch (ccb->ccb_h.func_code) {
+ case XPT_PATH_INQ:
+ {
+ struct ccb_pathinq *cpi = &ccb->cpi;
+
+ cpi->version_num = 1;
+ cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16;
+ cpi->target_sprt = 0;
+#if (__FreeBSD_version >= 1000039) || \
+ ((__FreeBSD_version < 1000000) && (__FreeBSD_version >= 902502))
+ cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED | PIM_NOSCAN;
+#else
+ cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED;
+#endif
+ cpi->hba_eng_cnt = 0;
+ cpi->max_target = sassc->maxtargets - 1;
+ cpi->max_lun = 255;
+ cpi->initiator_id = sassc->maxtargets - 1;
+ strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
+ strncpy(cpi->hba_vid, "LSILogic", HBA_IDLEN);
+ strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
+ cpi->unit_number = cam_sim_unit(sim);
+ cpi->bus_id = cam_sim_bus(sim);
+ /*
+ * XXXSLM-I think this needs to change based on config page or
+ * something instead of hardcoded to 150000.
+ */
+ cpi->base_transfer_speed = 150000;
+ cpi->transport = XPORT_SAS;
+ cpi->transport_version = 0;
+ cpi->protocol = PROTO_SCSI;
+ cpi->protocol_version = SCSI_REV_SPC;
+#if __FreeBSD_version >= 800001
+ /*
+ * XXXSLM-probably need to base this number on max SGL's and
+ * page size.
+ */
+ cpi->maxio = 256 * 1024;
+#endif
+ cpi->ccb_h.status = CAM_REQ_CMP;
+ break;
+ }
+ case XPT_GET_TRAN_SETTINGS:
+ {
+ struct ccb_trans_settings *cts;
+ struct ccb_trans_settings_sas *sas;
+ struct ccb_trans_settings_scsi *scsi;
+ struct mprsas_target *targ;
+
+ cts = &ccb->cts;
+ sas = &cts->xport_specific.sas;
+ scsi = &cts->proto_specific.scsi;
+
+ KASSERT(cts->ccb_h.target_id < sassc->maxtargets,
+ ("Target %d out of bounds in XPT_GET_TRAN_SETTINGS\n",
+ cts->ccb_h.target_id));
+ targ = &sassc->targets[cts->ccb_h.target_id];
+ if (targ->handle == 0x0) {
+ cts->ccb_h.status = CAM_DEV_NOT_THERE;
+ break;
+ }
+
+ cts->protocol_version = SCSI_REV_SPC2;
+ cts->transport = XPORT_SAS;
+ cts->transport_version = 0;
+
+ sas->valid = CTS_SAS_VALID_SPEED;
+ switch (targ->linkrate) {
+ case 0x08:
+ sas->bitrate = 150000;
+ break;
+ case 0x09:
+ sas->bitrate = 300000;
+ break;
+ case 0x0a:
+ sas->bitrate = 600000;
+ break;
+ default:
+ sas->valid = 0;
+ }
+
+ cts->protocol = PROTO_SCSI;
+ scsi->valid = CTS_SCSI_VALID_TQ;
+ scsi->flags = CTS_SCSI_FLAGS_TAG_ENB;
+
+ cts->ccb_h.status = CAM_REQ_CMP;
+ break;
+ }
+ case XPT_CALC_GEOMETRY:
+ cam_calc_geometry(&ccb->ccg, /*extended*/1);
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ break;
+ case XPT_RESET_DEV:
+ mpr_dprint(sassc->sc, MPR_XINFO,
+ "mprsas_action XPT_RESET_DEV\n");
+ mprsas_action_resetdev(sassc, ccb);
+ return;
+ case XPT_RESET_BUS:
+ case XPT_ABORT:
+ case XPT_TERM_IO:
+ mpr_dprint(sassc->sc, MPR_XINFO,
+ "mprsas_action faking success for abort or reset\n");
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ break;
+ case XPT_SCSI_IO:
+ mprsas_action_scsiio(sassc, ccb);
+ return;
+#if __FreeBSD_version >= 900026
+ case XPT_SMP_IO:
+ mprsas_action_smpio(sassc, ccb);
+ return;
+#endif
+ default:
+ ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
+ break;
+ }
+ xpt_done(ccb);
+
+}
+
+static void
+mprsas_announce_reset(struct mpr_softc *sc, uint32_t ac_code,
+ target_id_t target_id, lun_id_t lun_id)
+{
+ path_id_t path_id = cam_sim_path(sc->sassc->sim);
+ struct cam_path *path;
+
+ mpr_dprint(sc, MPR_XINFO, "%s code %x target %d lun %jx\n", __func__,
+ ac_code, target_id, (uintmax_t)lun_id);
+
+ if (xpt_create_path(&path, NULL,
+ path_id, target_id, lun_id) != CAM_REQ_CMP) {
+ mpr_dprint(sc, MPR_ERROR, "unable to create path for reset "
+ "notification\n");
+ return;
+ }
+
+ xpt_async(ac_code, path, NULL);
+ xpt_free_path(path);
+}
+
+static void
+mprsas_complete_all_commands(struct mpr_softc *sc)
+{
+ struct mpr_command *cm;
+ int i;
+ int completed;
+
+ MPR_FUNCTRACE(sc);
+ mtx_assert(&sc->mpr_mtx, MA_OWNED);
+
+ /* complete all commands with a NULL reply */
+ for (i = 1; i < sc->num_reqs; i++) {
+ cm = &sc->commands[i];
+ cm->cm_reply = NULL;
+ completed = 0;
+
+ if (cm->cm_flags & MPR_CM_FLAGS_POLLED)
+ cm->cm_flags |= MPR_CM_FLAGS_COMPLETE;
+
+ if (cm->cm_complete != NULL) {
+ mprsas_log_command(cm, MPR_RECOVERY,
+ "completing cm %p state %x ccb %p for diag reset\n",
+ cm, cm->cm_state, cm->cm_ccb);
+ cm->cm_complete(sc, cm);
+ completed = 1;
+ }
+
+ if (cm->cm_flags & MPR_CM_FLAGS_WAKEUP) {
+ mprsas_log_command(cm, MPR_RECOVERY,
+ "waking up cm %p state %x ccb %p for diag reset\n",
+ cm, cm->cm_state, cm->cm_ccb);
+ wakeup(cm);
+ completed = 1;
+ }
+
+ if ((completed == 0) && (cm->cm_state != MPR_CM_STATE_FREE)) {
+ /* this should never happen, but if it does, log */
+ mprsas_log_command(cm, MPR_RECOVERY,
+ "cm %p state %x flags 0x%x ccb %p during diag "
+ "reset\n", cm, cm->cm_state, cm->cm_flags,
+ cm->cm_ccb);
+ }
+ }
+}
+
+void
+mprsas_handle_reinit(struct mpr_softc *sc)
+{
+ int i;
+
+ /* Go back into startup mode and freeze the simq, so that CAM
+ * doesn't send any commands until after we've rediscovered all
+ * targets and found the proper device handles for them.
+ *
+ * After the reset, portenable will trigger discovery, and after all
+ * discovery-related activities have finished, the simq will be
+ * released.
+ */
+ mpr_dprint(sc, MPR_INIT, "%s startup\n", __func__);
+ sc->sassc->flags |= MPRSAS_IN_STARTUP;
+ sc->sassc->flags |= MPRSAS_IN_DISCOVERY;
+ mprsas_startup_increment(sc->sassc);
+
+ /* notify CAM of a bus reset */
+ mprsas_announce_reset(sc, AC_BUS_RESET, CAM_TARGET_WILDCARD,
+ CAM_LUN_WILDCARD);
+
+ /* complete and cleanup after all outstanding commands */
+ mprsas_complete_all_commands(sc);
+
+ mpr_dprint(sc, MPR_INIT, "%s startup %u tm %u after command "
+ "completion\n", __func__, sc->sassc->startup_refcount,
+ sc->sassc->tm_count);
+
+ /* zero all the target handles, since they may change after the
+ * reset, and we have to rediscover all the targets and use the new
+ * handles.
+ */
+ for (i = 0; i < sc->sassc->maxtargets; i++) {
+ if (sc->sassc->targets[i].outstanding != 0)
+ mpr_dprint(sc, MPR_INIT, "target %u outstanding %u\n",
+ i, sc->sassc->targets[i].outstanding);
+ sc->sassc->targets[i].handle = 0x0;
+ sc->sassc->targets[i].exp_dev_handle = 0x0;
+ sc->sassc->targets[i].outstanding = 0;
+ sc->sassc->targets[i].flags = MPRSAS_TARGET_INDIAGRESET;
+ }
+}
+static void
+mprsas_tm_timeout(void *data)
+{
+ struct mpr_command *tm = data;
+ struct mpr_softc *sc = tm->cm_sc;
+
+ mtx_assert(&sc->mpr_mtx, MA_OWNED);
+
+ mprsas_log_command(tm, MPR_INFO|MPR_RECOVERY,
+ "task mgmt %p timed out\n", tm);
+ mpr_reinit(sc);
+}
+
+static void
+mprsas_logical_unit_reset_complete(struct mpr_softc *sc,
+ struct mpr_command *tm)
+{
+ MPI2_SCSI_TASK_MANAGE_REPLY *reply;
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+ unsigned int cm_count = 0;
+ struct mpr_command *cm;
+ struct mprsas_target *targ;
+
+ callout_stop(&tm->cm_callout);
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
+ reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply;
+ targ = tm->cm_targ;
+
+ /*
+ * Currently there should be no way we can hit this case. It only
+ * happens when we have a failure to allocate chain frames, and
+ * task management commands don't have S/G lists.
+ */
+ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for LUN reset! "
+ "This should not happen!\n", __func__, tm->cm_flags);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ if (reply == NULL) {
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "NULL reset reply for tm %p\n", tm);
+ if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) {
+ /* this completion was due to a reset, just cleanup */
+ targ->flags &= ~MPRSAS_TARGET_INRESET;
+ targ->tm = NULL;
+ mprsas_free_tm(sc, tm);
+ }
+ else {
+ /* we should have gotten a reply. */
+ mpr_reinit(sc);
+ }
+ return;
+ }
+
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "logical unit reset status 0x%x code 0x%x count %u\n",
+ le16toh(reply->IOCStatus), le32toh(reply->ResponseCode),
+ le32toh(reply->TerminationCount));
+
+ /* See if there are any outstanding commands for this LUN.
+ * This could be made more efficient by using a per-LU data
+ * structure of some sort.
+ */
+ TAILQ_FOREACH(cm, &targ->commands, cm_link) {
+ if (cm->cm_lun == tm->cm_lun)
+ cm_count++;
+ }
+
+ if (cm_count == 0) {
+ mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO,
+ "logical unit %u finished recovery after reset\n",
+ tm->cm_lun, tm);
+
+ mprsas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid,
+ tm->cm_lun);
+
+ /* we've finished recovery for this logical unit. check and
+ * see if some other logical unit has a timedout command
+ * that needs to be processed.
+ */
+ cm = TAILQ_FIRST(&targ->timedout_commands);
+ if (cm) {
+ mprsas_send_abort(sc, tm, cm);
+ }
+ else {
+ targ->tm = NULL;
+ mprsas_free_tm(sc, tm);
+ }
+ }
+ else {
+ /* if we still have commands for this LUN, the reset
+ * effectively failed, regardless of the status reported.
+ * Escalate to a target reset.
+ */
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "logical unit reset complete for tm %p, but still have %u "
+ "command(s)\n", tm, cm_count);
+ mprsas_send_reset(sc, tm,
+ MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET);
+ }
+}
+
+static void
+mprsas_target_reset_complete(struct mpr_softc *sc, struct mpr_command *tm)
+{
+ MPI2_SCSI_TASK_MANAGE_REPLY *reply;
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+ struct mprsas_target *targ;
+
+ callout_stop(&tm->cm_callout);
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
+ reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply;
+ targ = tm->cm_targ;
+
+ /*
+ * Currently there should be no way we can hit this case. It only
+ * happens when we have a failure to allocate chain frames, and
+ * task management commands don't have S/G lists.
+ */
+ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ mpr_dprint(sc, MPR_ERROR,"%s: cm_flags = %#x for target reset! "
+ "This should not happen!\n", __func__, tm->cm_flags);
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ if (reply == NULL) {
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "NULL reset reply for tm %p\n", tm);
+ if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) {
+ /* this completion was due to a reset, just cleanup */
+ targ->flags &= ~MPRSAS_TARGET_INRESET;
+ targ->tm = NULL;
+ mprsas_free_tm(sc, tm);
+ }
+ else {
+ /* we should have gotten a reply. */
+ mpr_reinit(sc);
+ }
+ return;
+ }
+
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "target reset status 0x%x code 0x%x count %u\n",
+ le16toh(reply->IOCStatus), le32toh(reply->ResponseCode),
+ le32toh(reply->TerminationCount));
+
+ targ->flags &= ~MPRSAS_TARGET_INRESET;
+
+ if (targ->outstanding == 0) {
+ /* we've finished recovery for this target and all
+ * of its logical units.
+ */
+ mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO,
+ "recovery finished after target reset\n");
+
+ mprsas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid,
+ CAM_LUN_WILDCARD);
+
+ targ->tm = NULL;
+ mprsas_free_tm(sc, tm);
+ }
+ else {
+ /* after a target reset, if this target still has
+ * outstanding commands, the reset effectively failed,
+ * regardless of the status reported. escalate.
+ */
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "target reset complete for tm %p, but still have %u "
+ "command(s)\n", tm, targ->outstanding);
+ mpr_reinit(sc);
+ }
+}
+
+#define MPR_RESET_TIMEOUT 30
+
+static int
+mprsas_send_reset(struct mpr_softc *sc, struct mpr_command *tm, uint8_t type)
+{
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+ struct mprsas_target *target;
+ int err;
+
+ target = tm->cm_targ;
+ if (target->handle == 0) {
+ mpr_dprint(sc, MPR_ERROR,"%s null devhandle for target_id %d\n",
+ __func__, target->tid);
+ return -1;
+ }
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
+ req->DevHandle = htole16(target->handle);
+ req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT;
+ req->TaskType = type;
+
+ if (type == MPI2_SCSITASKMGMT_TASKTYPE_LOGICAL_UNIT_RESET) {
+ /* XXX Need to handle invalid LUNs */
+ MPR_SET_LUN(req->LUN, tm->cm_lun);
+ tm->cm_targ->logical_unit_resets++;
+ mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO,
+ "sending logical unit reset\n");
+ tm->cm_complete = mprsas_logical_unit_reset_complete;
+ }
+ else if (type == MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET) {
+ /*
+ * Target reset method =
+ * SAS Hard Link Reset / SATA Link Reset
+ */
+ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET;
+ tm->cm_targ->target_resets++;
+ tm->cm_targ->flags |= MPRSAS_TARGET_INRESET;
+ mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO,
+ "sending target reset\n");
+ tm->cm_complete = mprsas_target_reset_complete;
+ }
+ else {
+ mpr_dprint(sc, MPR_ERROR, "unexpected reset type 0x%x\n", type);
+ return -1;
+ }
+
+ mpr_dprint(sc, MPR_XINFO, "to target %u handle 0x%04x\n", target->tid,
+ target->handle);
+ if (target->encl_level_valid) {
+ mpr_dprint(sc, MPR_XINFO, "At enclosure level %d, slot %d, "
+ "connector name (%4s)\n", target->encl_level,
+ target->encl_slot, target->connector_name);
+ }
+
+ tm->cm_data = NULL;
+ tm->cm_desc.HighPriority.RequestFlags =
+ MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY;
+ tm->cm_complete_data = (void *)tm;
+
+ callout_reset(&tm->cm_callout, MPR_RESET_TIMEOUT * hz,
+ mprsas_tm_timeout, tm);
+
+ err = mpr_map_command(sc, tm);
+ if (err)
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "error %d sending reset type %u\n",
+ err, type);
+
+ return err;
+}
+
+
+static void
+mprsas_abort_complete(struct mpr_softc *sc, struct mpr_command *tm)
+{
+ struct mpr_command *cm;
+ MPI2_SCSI_TASK_MANAGE_REPLY *reply;
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+ struct mprsas_target *targ;
+
+ callout_stop(&tm->cm_callout);
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
+ reply = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply;
+ targ = tm->cm_targ;
+
+ /*
+ * Currently there should be no way we can hit this case. It only
+ * happens when we have a failure to allocate chain frames, and
+ * task management commands don't have S/G lists.
+ */
+ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "cm_flags = %#x for abort %p TaskMID %u!\n",
+ tm->cm_flags, tm, le16toh(req->TaskMID));
+ mprsas_free_tm(sc, tm);
+ return;
+ }
+
+ if (reply == NULL) {
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "NULL abort reply for tm %p TaskMID %u\n",
+ tm, le16toh(req->TaskMID));
+ if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) {
+ /* this completion was due to a reset, just cleanup */
+ targ->tm = NULL;
+ mprsas_free_tm(sc, tm);
+ }
+ else {
+ /* we should have gotten a reply. */
+ mpr_reinit(sc);
+ }
+ return;
+ }
+
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "abort TaskMID %u status 0x%x code 0x%x count %u\n",
+ le16toh(req->TaskMID),
+ le16toh(reply->IOCStatus), le32toh(reply->ResponseCode),
+ le32toh(reply->TerminationCount));
+
+ cm = TAILQ_FIRST(&tm->cm_targ->timedout_commands);
+ if (cm == NULL) {
+ /* if there are no more timedout commands, we're done with
+ * error recovery for this target.
+ */
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "finished recovery after aborting TaskMID %u\n",
+ le16toh(req->TaskMID));
+
+ targ->tm = NULL;
+ mprsas_free_tm(sc, tm);
+ }
+ else if (le16toh(req->TaskMID) != cm->cm_desc.Default.SMID) {
+ /* abort success, but we have more timedout commands to abort */
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "continuing recovery after aborting TaskMID %u\n",
+ le16toh(req->TaskMID));
+
+ mprsas_send_abort(sc, tm, cm);
+ }
+ else {
+ /* we didn't get a command completion, so the abort
+ * failed as far as we're concerned. escalate.
+ */
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "abort failed for TaskMID %u tm %p\n",
+ le16toh(req->TaskMID), tm);
+
+ mprsas_send_reset(sc, tm,
+ MPI2_SCSITASKMGMT_TASKTYPE_LOGICAL_UNIT_RESET);
+ }
+}
+
+#define MPR_ABORT_TIMEOUT 5
+
+static int
+mprsas_send_abort(struct mpr_softc *sc, struct mpr_command *tm,
+ struct mpr_command *cm)
+{
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+ struct mprsas_target *targ;
+ int err;
+
+ targ = cm->cm_targ;
+ if (targ->handle == 0) {
+ mpr_dprint(sc, MPR_ERROR,"%s null devhandle for target_id %d\n",
+ __func__, cm->cm_ccb->ccb_h.target_id);
+ return -1;
+ }
+
+ mprsas_log_command(tm, MPR_RECOVERY|MPR_INFO,
+ "Aborting command %p\n", cm);
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
+ req->DevHandle = htole16(targ->handle);
+ req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT;
+ req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_ABORT_TASK;
+
+ /* XXX Need to handle invalid LUNs */
+ MPR_SET_LUN(req->LUN, cm->cm_ccb->ccb_h.target_lun);
+
+ req->TaskMID = htole16(cm->cm_desc.Default.SMID);
+
+ tm->cm_data = NULL;
+ tm->cm_desc.HighPriority.RequestFlags =
+ MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY;
+ tm->cm_complete = mprsas_abort_complete;
+ tm->cm_complete_data = (void *)tm;
+ tm->cm_targ = cm->cm_targ;
+ tm->cm_lun = cm->cm_lun;
+
+ callout_reset(&tm->cm_callout, MPR_ABORT_TIMEOUT * hz,
+ mprsas_tm_timeout, tm);
+
+ targ->aborts++;
+
+ err = mpr_map_command(sc, tm);
+ if (err)
+ mprsas_log_command(tm, MPR_RECOVERY,
+ "error %d sending abort for cm %p SMID %u\n",
+ err, cm, req->TaskMID);
+ return err;
+}
+
+
+static void
+mprsas_scsiio_timeout(void *data)
+{
+ struct mpr_softc *sc;
+ struct mpr_command *cm;
+ struct mprsas_target *targ;
+
+ cm = (struct mpr_command *)data;
+ sc = cm->cm_sc;
+
+ MPR_FUNCTRACE(sc);
+ mtx_assert(&sc->mpr_mtx, MA_OWNED);
+
+ mpr_dprint(sc, MPR_XINFO, "Timeout checking cm %p\n", cm);
+
+ /*
+ * Run the interrupt handler to make sure it's not pending. This
+ * isn't perfect because the command could have already completed
+ * and been re-used, though this is unlikely.
+ */
+ mpr_intr_locked(sc);
+ if (cm->cm_state == MPR_CM_STATE_FREE) {
+ mprsas_log_command(cm, MPR_XINFO,
+ "SCSI command %p almost timed out\n", cm);
+ return;
+ }
+
+ if (cm->cm_ccb == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "command timeout with NULL ccb\n");
+ return;
+ }
+
+ targ = cm->cm_targ;
+ targ->timeouts++;
+
+ mprsas_log_command(cm, MPR_XINFO, "command timeout cm %p ccb %p "
+ "target %u, handle(0x%04x)\n", cm, cm->cm_ccb, targ->tid,
+ targ->handle);
+ if (targ->encl_level_valid) {
+ mpr_dprint(sc, MPR_XINFO, "At enclosure level %d, slot %d, "
+ "connector name (%4s)\n", targ->encl_level, targ->encl_slot,
+ targ->connector_name);
+ }
+
+ /* XXX first, check the firmware state, to see if it's still
+ * operational. if not, do a diag reset.
+ */
+
+ cm->cm_ccb->ccb_h.status = CAM_CMD_TIMEOUT;
+ cm->cm_state = MPR_CM_STATE_TIMEDOUT;
+ TAILQ_INSERT_TAIL(&targ->timedout_commands, cm, cm_recovery);
+
+ if (targ->tm != NULL) {
+ /* target already in recovery, just queue up another
+ * timedout command to be processed later.
+ */
+ mpr_dprint(sc, MPR_RECOVERY, "queued timedout cm %p for "
+ "processing by tm %p\n", cm, targ->tm);
+ }
+ else if ((targ->tm = mprsas_alloc_tm(sc)) != NULL) {
+ mpr_dprint(sc, MPR_RECOVERY, "timedout cm %p allocated tm %p\n",
+ cm, targ->tm);
+
+ /* start recovery by aborting the first timedout command */
+ mprsas_send_abort(sc, targ->tm, cm);
+ }
+ else {
+ /* XXX queue this target up for recovery once a TM becomes
+ * available. The firmware only has a limited number of
+ * HighPriority credits for the high priority requests used
+ * for task management, and we ran out.
+ *
+ * Isilon: don't worry about this for now, since we have
+ * more credits than disks in an enclosure, and limit
+ * ourselves to one TM per target for recovery.
+ */
+ mpr_dprint(sc, MPR_RECOVERY,
+ "timedout cm %p failed to allocate a tm\n", cm);
+ }
+}
+
+static void
+mprsas_action_scsiio(struct mprsas_softc *sassc, union ccb *ccb)
+{
+ MPI2_SCSI_IO_REQUEST *req;
+ struct ccb_scsiio *csio;
+ struct mpr_softc *sc;
+ struct mprsas_target *targ;
+ struct mprsas_lun *lun;
+ struct mpr_command *cm;
+ uint8_t i, lba_byte, *ref_tag_addr;
+ uint16_t eedp_flags;
+ uint32_t mpi_control;
+
+ sc = sassc->sc;
+ MPR_FUNCTRACE(sc);
+ mtx_assert(&sc->mpr_mtx, MA_OWNED);
+
+ csio = &ccb->csio;
+ targ = &sassc->targets[csio->ccb_h.target_id];
+ mpr_dprint(sc, MPR_TRACE, "ccb %p target flag %x\n", ccb, targ->flags);
+ if (targ->handle == 0x0) {
+ mpr_dprint(sc, MPR_ERROR, "%s NULL handle for target %u\n",
+ __func__, csio->ccb_h.target_id);
+ csio->ccb_h.status = CAM_DEV_NOT_THERE;
+ xpt_done(ccb);
+ return;
+ }
+ if (targ->flags & MPR_TARGET_FLAGS_RAID_COMPONENT) {
+ mpr_dprint(sc, MPR_TRACE, "%s Raid component no SCSI IO "
+ "supported %u\n", __func__, csio->ccb_h.target_id);
+ csio->ccb_h.status = CAM_DEV_NOT_THERE;
+ xpt_done(ccb);
+ return;
+ }
+ /*
+ * Sometimes, it is possible to get a command that is not "In
+ * Progress" and was actually aborted by the upper layer. Check for
+ * this here and complete the command without error.
+ */
+ if (ccb->ccb_h.status != CAM_REQ_INPROG) {
+ mpr_dprint(sc, MPR_TRACE, "%s Command is not in progress for "
+ "target %u\n", __func__, csio->ccb_h.target_id);
+ xpt_done(ccb);
+ return;
+ }
+ /*
+ * If devinfo is 0 this will be a volume. In that case don't tell CAM
+ * that the volume has timed out. We want volumes to be enumerated
+ * until they are deleted/removed, not just failed.
+ */
+ if (targ->flags & MPRSAS_TARGET_INREMOVAL) {
+ if (targ->devinfo == 0)
+ csio->ccb_h.status = CAM_REQ_CMP;
+ else
+ csio->ccb_h.status = CAM_SEL_TIMEOUT;
+ xpt_done(ccb);
+ return;
+ }
+
+ if ((sc->mpr_flags & MPR_FLAGS_SHUTDOWN) != 0) {
+ mpr_dprint(sc, MPR_TRACE, "%s shutting down\n", __func__);
+ csio->ccb_h.status = CAM_DEV_NOT_THERE;
+ xpt_done(ccb);
+ return;
+ }
+
+ cm = mpr_alloc_command(sc);
+ if (cm == NULL || (sc->mpr_flags & MPR_FLAGS_DIAGRESET)) {
+ if (cm != NULL) {
+ mpr_free_command(sc, cm);
+ }
+ if ((sassc->flags & MPRSAS_QUEUE_FROZEN) == 0) {
+ xpt_freeze_simq(sassc->sim, 1);
+ sassc->flags |= MPRSAS_QUEUE_FROZEN;
+ }
+ ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
+ ccb->ccb_h.status |= CAM_REQUEUE_REQ;
+ xpt_done(ccb);
+ return;
+ }
+
+ req = (MPI2_SCSI_IO_REQUEST *)cm->cm_req;
+ bzero(req, sizeof(*req));
+ req->DevHandle = htole16(targ->handle);
+ req->Function = MPI2_FUNCTION_SCSI_IO_REQUEST;
+ req->MsgFlags = 0;
+ req->SenseBufferLowAddress = htole32(cm->cm_sense_busaddr);
+ req->SenseBufferLength = MPR_SENSE_LEN;
+ req->SGLFlags = 0;
+ req->ChainOffset = 0;
+ req->SGLOffset0 = 24; /* 32bit word offset to the SGL */
+ req->SGLOffset1= 0;
+ req->SGLOffset2= 0;
+ req->SGLOffset3= 0;
+ req->SkipCount = 0;
+ req->DataLength = htole32(csio->dxfer_len);
+ req->BidirectionalDataLength = 0;
+ req->IoFlags = htole16(csio->cdb_len);
+ req->EEDPFlags = 0;
+
+ /* Note: BiDirectional transfers are not supported */
+ switch (csio->ccb_h.flags & CAM_DIR_MASK) {
+ case CAM_DIR_IN:
+ mpi_control = MPI2_SCSIIO_CONTROL_READ;
+ cm->cm_flags |= MPR_CM_FLAGS_DATAIN;
+ break;
+ case CAM_DIR_OUT:
+ mpi_control = MPI2_SCSIIO_CONTROL_WRITE;
+ cm->cm_flags |= MPR_CM_FLAGS_DATAOUT;
+ break;
+ case CAM_DIR_NONE:
+ default:
+ mpi_control = MPI2_SCSIIO_CONTROL_NODATATRANSFER;
+ break;
+ }
+
+ if (csio->cdb_len == 32)
+ mpi_control |= 4 << MPI2_SCSIIO_CONTROL_ADDCDBLEN_SHIFT;
+ /*
+ * It looks like the hardware doesn't require an explicit tag
+ * number for each transaction. SAM Task Management not supported
+ * at the moment.
+ */
+ switch (csio->tag_action) {
+ case MSG_HEAD_OF_Q_TAG:
+ mpi_control |= MPI2_SCSIIO_CONTROL_HEADOFQ;
+ break;
+ case MSG_ORDERED_Q_TAG:
+ mpi_control |= MPI2_SCSIIO_CONTROL_ORDEREDQ;
+ break;
+ case MSG_ACA_TASK:
+ mpi_control |= MPI2_SCSIIO_CONTROL_ACAQ;
+ break;
+ case CAM_TAG_ACTION_NONE:
+ case MSG_SIMPLE_Q_TAG:
+ default:
+ mpi_control |= MPI2_SCSIIO_CONTROL_SIMPLEQ;
+ break;
+ }
+ mpi_control |= sc->mapping_table[csio->ccb_h.target_id].TLR_bits;
+ req->Control = htole32(mpi_control);
+
+ if (MPR_SET_LUN(req->LUN, csio->ccb_h.target_lun) != 0) {
+ mpr_free_command(sc, cm);
+ ccb->ccb_h.status = CAM_LUN_INVALID;
+ xpt_done(ccb);
+ return;
+ }
+
+ if (csio->ccb_h.flags & CAM_CDB_POINTER)
+ bcopy(csio->cdb_io.cdb_ptr, &req->CDB.CDB32[0], csio->cdb_len);
+ else
+ bcopy(csio->cdb_io.cdb_bytes, &req->CDB.CDB32[0],csio->cdb_len);
+ req->IoFlags = htole16(csio->cdb_len);
+
+ /*
+ * Check if EEDP is supported and enabled. If it is then check if the
+ * SCSI opcode could be using EEDP. If so, make sure the LUN exists and
+ * is formatted for EEDP support. If all of this is true, set CDB up
+ * for EEDP transfer.
+ */
+ eedp_flags = op_code_prot[req->CDB.CDB32[0]];
+ if (sc->eedp_enabled && eedp_flags) {
+ SLIST_FOREACH(lun, &targ->luns, lun_link) {
+ if (lun->lun_id == csio->ccb_h.target_lun) {
+ break;
+ }
+ }
+
+ if ((lun != NULL) && (lun->eedp_formatted)) {
+ req->EEDPBlockSize = htole16(lun->eedp_block_size);
+ eedp_flags |= (MPI2_SCSIIO_EEDPFLAGS_INC_PRI_REFTAG |
+ MPI2_SCSIIO_EEDPFLAGS_CHECK_REFTAG |
+ MPI2_SCSIIO_EEDPFLAGS_CHECK_GUARD);
+ req->EEDPFlags = htole16(eedp_flags);
+
+ /*
+ * If CDB less than 32, fill in Primary Ref Tag with
+ * low 4 bytes of LBA. If CDB is 32, tag stuff is
+ * already there. Also, set protection bit. FreeBSD
+ * currently does not support CDBs bigger than 16, but
+ * the code doesn't hurt, and will be here for the
+ * future.
+ */
+ if (csio->cdb_len != 32) {
+ lba_byte = (csio->cdb_len == 16) ? 6 : 2;
+ ref_tag_addr = (uint8_t *)&req->CDB.EEDP32.
+ PrimaryReferenceTag;
+ for (i = 0; i < 4; i++) {
+ *ref_tag_addr =
+ req->CDB.CDB32[lba_byte + i];
+ ref_tag_addr++;
+ }
+ req->CDB.EEDP32.PrimaryReferenceTag =
+ htole32(req->
+ CDB.EEDP32.PrimaryReferenceTag);
+ req->CDB.EEDP32.PrimaryApplicationTagMask =
+ 0xFFFF;
+ req->CDB.CDB32[1] = (req->CDB.CDB32[1] & 0x1F) |
+ 0x20;
+ } else {
+ eedp_flags |=
+ MPI2_SCSIIO_EEDPFLAGS_INC_PRI_APPTAG;
+ req->EEDPFlags = htole16(eedp_flags);
+ req->CDB.CDB32[10] = (req->CDB.CDB32[10] &
+ 0x1F) | 0x20;
+ }
+ }
+ }
+
+ cm->cm_length = csio->dxfer_len;
+ if (cm->cm_length != 0) {
+ cm->cm_data = ccb;
+ cm->cm_flags |= MPR_CM_FLAGS_USE_CCB;
+ } else {
+ cm->cm_data = NULL;
+ }
+ cm->cm_sge = &req->SGL;
+ cm->cm_sglsize = (32 - 24) * 4;
+ cm->cm_complete = mprsas_scsiio_complete;
+ cm->cm_complete_data = ccb;
+ cm->cm_targ = targ;
+ cm->cm_lun = csio->ccb_h.target_lun;
+ cm->cm_ccb = ccb;
+ /*
+ * If using FP desc type, need to set a bit in IoFlags (SCSI IO is 0)
+ * and set descriptor type.
+ */
+ if (targ->scsi_req_desc_type ==
+ MPI25_REQ_DESCRIPT_FLAGS_FAST_PATH_SCSI_IO) {
+ req->IoFlags |= MPI25_SCSIIO_IOFLAGS_FAST_PATH;
+ cm->cm_desc.FastPathSCSIIO.RequestFlags =
+ MPI25_REQ_DESCRIPT_FLAGS_FAST_PATH_SCSI_IO;
+ cm->cm_desc.FastPathSCSIIO.DevHandle = htole16(targ->handle);
+ } else {
+ cm->cm_desc.SCSIIO.RequestFlags =
+ MPI2_REQ_DESCRIPT_FLAGS_SCSI_IO;
+ cm->cm_desc.SCSIIO.DevHandle = htole16(targ->handle);
+ }
+
+ callout_reset(&cm->cm_callout, (ccb->ccb_h.timeout * hz) / 1000,
+ mprsas_scsiio_timeout, cm);
+
+ targ->issued++;
+ targ->outstanding++;
+ TAILQ_INSERT_TAIL(&targ->commands, cm, cm_link);
+ ccb->ccb_h.status |= CAM_SIM_QUEUED;
+
+ mprsas_log_command(cm, MPR_XINFO, "%s cm %p ccb %p outstanding %u\n",
+ __func__, cm, ccb, targ->outstanding);
+
+ mpr_map_command(sc, cm);
+ return;
+}
+
+static void
+mpr_response_code(struct mpr_softc *sc, u8 response_code)
+{
+ char *desc;
+
+ switch (response_code) {
+ case MPI2_SCSITASKMGMT_RSP_TM_COMPLETE:
+ desc = "task management request completed";
+ break;
+ case MPI2_SCSITASKMGMT_RSP_INVALID_FRAME:
+ desc = "invalid frame";
+ break;
+ case MPI2_SCSITASKMGMT_RSP_TM_NOT_SUPPORTED:
+ desc = "task management request not supported";
+ break;
+ case MPI2_SCSITASKMGMT_RSP_TM_FAILED:
+ desc = "task management request failed";
+ break;
+ case MPI2_SCSITASKMGMT_RSP_TM_SUCCEEDED:
+ desc = "task management request succeeded";
+ break;
+ case MPI2_SCSITASKMGMT_RSP_TM_INVALID_LUN:
+ desc = "invalid lun";
+ break;
+ case 0xA:
+ desc = "overlapped tag attempted";
+ break;
+ case MPI2_SCSITASKMGMT_RSP_IO_QUEUED_ON_IOC:
+ desc = "task queued, however not sent to target";
+ break;
+ default:
+ desc = "unknown";
+ break;
+ }
+ mpr_dprint(sc, MPR_XINFO, "response_code(0x%01x): %s\n", response_code,
+ desc);
+}
+
+/**
+ * mpr_sc_failed_io_info - translated non-succesfull SCSI_IO request
+ */
+static void
+mpr_sc_failed_io_info(struct mpr_softc *sc, struct ccb_scsiio *csio,
+ Mpi2SCSIIOReply_t *mpi_reply, struct mprsas_target *targ)
+{
+ u32 response_info;
+ u8 *response_bytes;
+ u16 ioc_status = le16toh(mpi_reply->IOCStatus) &
+ MPI2_IOCSTATUS_MASK;
+ u8 scsi_state = mpi_reply->SCSIState;
+ u8 scsi_status = mpi_reply->SCSIStatus;
+ char *desc_ioc_state = NULL;
+ char *desc_scsi_status = NULL;
+ char *desc_scsi_state = sc->tmp_string;
+ u32 log_info = le32toh(mpi_reply->IOCLogInfo);
+
+ if (log_info == 0x31170000)
+ return;
+
+ switch (ioc_status) {
+ case MPI2_IOCSTATUS_SUCCESS:
+ desc_ioc_state = "success";
+ break;
+ case MPI2_IOCSTATUS_INVALID_FUNCTION:
+ desc_ioc_state = "invalid function";
+ break;
+ case MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR:
+ desc_ioc_state = "scsi recovered error";
+ break;
+ case MPI2_IOCSTATUS_SCSI_INVALID_DEVHANDLE:
+ desc_ioc_state = "scsi invalid dev handle";
+ break;
+ case MPI2_IOCSTATUS_SCSI_DEVICE_NOT_THERE:
+ desc_ioc_state = "scsi device not there";
+ break;
+ case MPI2_IOCSTATUS_SCSI_DATA_OVERRUN:
+ desc_ioc_state = "scsi data overrun";
+ break;
+ case MPI2_IOCSTATUS_SCSI_DATA_UNDERRUN:
+ desc_ioc_state = "scsi data underrun";
+ break;
+ case MPI2_IOCSTATUS_SCSI_IO_DATA_ERROR:
+ desc_ioc_state = "scsi io data error";
+ break;
+ case MPI2_IOCSTATUS_SCSI_PROTOCOL_ERROR:
+ desc_ioc_state = "scsi protocol error";
+ break;
+ case MPI2_IOCSTATUS_SCSI_TASK_TERMINATED:
+ desc_ioc_state = "scsi task terminated";
+ break;
+ case MPI2_IOCSTATUS_SCSI_RESIDUAL_MISMATCH:
+ desc_ioc_state = "scsi residual mismatch";
+ break;
+ case MPI2_IOCSTATUS_SCSI_TASK_MGMT_FAILED:
+ desc_ioc_state = "scsi task mgmt failed";
+ break;
+ case MPI2_IOCSTATUS_SCSI_IOC_TERMINATED:
+ desc_ioc_state = "scsi ioc terminated";
+ break;
+ case MPI2_IOCSTATUS_SCSI_EXT_TERMINATED:
+ desc_ioc_state = "scsi ext terminated";
+ break;
+ case MPI2_IOCSTATUS_EEDP_GUARD_ERROR:
+ desc_ioc_state = "eedp guard error";
+ break;
+ case MPI2_IOCSTATUS_EEDP_REF_TAG_ERROR:
+ desc_ioc_state = "eedp ref tag error";
+ break;
+ case MPI2_IOCSTATUS_EEDP_APP_TAG_ERROR:
+ desc_ioc_state = "eedp app tag error";
+ break;
+ default:
+ desc_ioc_state = "unknown";
+ break;
+ }
+
+ switch (scsi_status) {
+ case MPI2_SCSI_STATUS_GOOD:
+ desc_scsi_status = "good";
+ break;
+ case MPI2_SCSI_STATUS_CHECK_CONDITION:
+ desc_scsi_status = "check condition";
+ break;
+ case MPI2_SCSI_STATUS_CONDITION_MET:
+ desc_scsi_status = "condition met";
+ break;
+ case MPI2_SCSI_STATUS_BUSY:
+ desc_scsi_status = "busy";
+ break;
+ case MPI2_SCSI_STATUS_INTERMEDIATE:
+ desc_scsi_status = "intermediate";
+ break;
+ case MPI2_SCSI_STATUS_INTERMEDIATE_CONDMET:
+ desc_scsi_status = "intermediate condmet";
+ break;
+ case MPI2_SCSI_STATUS_RESERVATION_CONFLICT:
+ desc_scsi_status = "reservation conflict";
+ break;
+ case MPI2_SCSI_STATUS_COMMAND_TERMINATED:
+ desc_scsi_status = "command terminated";
+ break;
+ case MPI2_SCSI_STATUS_TASK_SET_FULL:
+ desc_scsi_status = "task set full";
+ break;
+ case MPI2_SCSI_STATUS_ACA_ACTIVE:
+ desc_scsi_status = "aca active";
+ break;
+ case MPI2_SCSI_STATUS_TASK_ABORTED:
+ desc_scsi_status = "task aborted";
+ break;
+ default:
+ desc_scsi_status = "unknown";
+ break;
+ }
+
+ desc_scsi_state[0] = '\0';
+ if (!scsi_state)
+ desc_scsi_state = " ";
+ if (scsi_state & MPI2_SCSI_STATE_RESPONSE_INFO_VALID)
+ strcat(desc_scsi_state, "response info ");
+ if (scsi_state & MPI2_SCSI_STATE_TERMINATED)
+ strcat(desc_scsi_state, "state terminated ");
+ if (scsi_state & MPI2_SCSI_STATE_NO_SCSI_STATUS)
+ strcat(desc_scsi_state, "no status ");
+ if (scsi_state & MPI2_SCSI_STATE_AUTOSENSE_FAILED)
+ strcat(desc_scsi_state, "autosense failed ");
+ if (scsi_state & MPI2_SCSI_STATE_AUTOSENSE_VALID)
+ strcat(desc_scsi_state, "autosense valid ");
+
+ mpr_dprint(sc, MPR_XINFO, "\thandle(0x%04x), ioc_status(%s)(0x%04x)\n",
+ le16toh(mpi_reply->DevHandle), desc_ioc_state, ioc_status);
+ if (targ->encl_level_valid) {
+ mpr_dprint(sc, MPR_XINFO, "At enclosure level %d, slot %d, "
+ "connector name (%4s)\n", targ->encl_level, targ->encl_slot,
+ targ->connector_name);
+ }
+ /* We can add more detail about underflow data here
+ * TO-DO
+ * */
+ mpr_dprint(sc, MPR_XINFO, "\tscsi_status(%s)(0x%02x), "
+ "scsi_state(%s)(0x%02x)\n", desc_scsi_status, scsi_status,
+ desc_scsi_state, scsi_state);
+
+ if (sc->mpr_debug & MPR_XINFO &&
+ scsi_state & MPI2_SCSI_STATE_AUTOSENSE_VALID) {
+ mpr_dprint(sc, MPR_XINFO, "-> Sense Buffer Data : Start :\n");
+ scsi_sense_print(csio);
+ mpr_dprint(sc, MPR_XINFO, "-> Sense Buffer Data : End :\n");
+ }
+
+ if (scsi_state & MPI2_SCSI_STATE_RESPONSE_INFO_VALID) {
+ response_info = le32toh(mpi_reply->ResponseInfo);
+ response_bytes = (u8 *)&response_info;
+ mpr_response_code(sc,response_bytes[0]);
+ }
+}
+
+static void
+mprsas_scsiio_complete(struct mpr_softc *sc, struct mpr_command *cm)
+{
+ MPI2_SCSI_IO_REPLY *rep;
+ union ccb *ccb;
+ struct ccb_scsiio *csio;
+ struct mprsas_softc *sassc;
+ struct scsi_vpd_supported_page_list *vpd_list = NULL;
+ u8 *TLR_bits, TLR_on;
+ int dir = 0, i;
+ u16 alloc_len;
+
+ MPR_FUNCTRACE(sc);
+ mpr_dprint(sc, MPR_TRACE,
+ "cm %p SMID %u ccb %p reply %p outstanding %u\n", cm,
+ cm->cm_desc.Default.SMID, cm->cm_ccb, cm->cm_reply,
+ cm->cm_targ->outstanding);
+
+ callout_stop(&cm->cm_callout);
+ mtx_assert(&sc->mpr_mtx, MA_OWNED);
+
+ sassc = sc->sassc;
+ ccb = cm->cm_complete_data;
+ csio = &ccb->csio;
+ rep = (MPI2_SCSI_IO_REPLY *)cm->cm_reply;
+ /*
+ * XXX KDM if the chain allocation fails, does it matter if we do
+ * the sync and unload here? It is simpler to do it in every case,
+ * assuming it doesn't cause problems.
+ */
+ if (cm->cm_data != NULL) {
+ if (cm->cm_flags & MPR_CM_FLAGS_DATAIN)
+ dir = BUS_DMASYNC_POSTREAD;
+ else if (cm->cm_flags & MPR_CM_FLAGS_DATAOUT)
+ dir = BUS_DMASYNC_POSTWRITE;
+ bus_dmamap_sync(sc->buffer_dmat, cm->cm_dmamap, dir);
+ bus_dmamap_unload(sc->buffer_dmat, cm->cm_dmamap);
+ }
+
+ cm->cm_targ->completed++;
+ cm->cm_targ->outstanding--;
+ TAILQ_REMOVE(&cm->cm_targ->commands, cm, cm_link);
+ ccb->ccb_h.status &= ~(CAM_STATUS_MASK | CAM_SIM_QUEUED);
+
+ if (cm->cm_state == MPR_CM_STATE_TIMEDOUT) {
+ TAILQ_REMOVE(&cm->cm_targ->timedout_commands, cm, cm_recovery);
+ if (cm->cm_reply != NULL)
+ mprsas_log_command(cm, MPR_RECOVERY,
+ "completed timedout cm %p ccb %p during recovery "
+ "ioc %x scsi %x state %x xfer %u\n", cm, cm->cm_ccb,
+ le16toh(rep->IOCStatus), rep->SCSIStatus,
+ rep->SCSIState, le32toh(rep->TransferCount));
+ else
+ mprsas_log_command(cm, MPR_RECOVERY,
+ "completed timedout cm %p ccb %p during recovery\n",
+ cm, cm->cm_ccb);
+ } else if (cm->cm_targ->tm != NULL) {
+ if (cm->cm_reply != NULL)
+ mprsas_log_command(cm, MPR_RECOVERY,
+ "completed cm %p ccb %p during recovery "
+ "ioc %x scsi %x state %x xfer %u\n",
+ cm, cm->cm_ccb, le16toh(rep->IOCStatus),
+ rep->SCSIStatus, rep->SCSIState,
+ le32toh(rep->TransferCount));
+ else
+ mprsas_log_command(cm, MPR_RECOVERY,
+ "completed cm %p ccb %p during recovery\n",
+ cm, cm->cm_ccb);
+ } else if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0) {
+ mprsas_log_command(cm, MPR_RECOVERY,
+ "reset completed cm %p ccb %p\n", cm, cm->cm_ccb);
+ }
+
+ if ((cm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ /*
+ * We ran into an error after we tried to map the command,
+ * so we're getting a callback without queueing the command
+ * to the hardware. So we set the status here, and it will
+ * be retained below. We'll go through the "fast path",
+ * because there can be no reply when we haven't actually
+ * gone out to the hardware.
+ */
+ ccb->ccb_h.status = CAM_REQUEUE_REQ;
+
+ /*
+ * Currently the only error included in the mask is
+ * MPR_CM_FLAGS_CHAIN_FAILED, which means we're out of
+ * chain frames. We need to freeze the queue until we get
+ * a command that completed without this error, which will
+ * hopefully have some chain frames attached that we can
+ * use. If we wanted to get smarter about it, we would
+ * only unfreeze the queue in this condition when we're
+ * sure that we're getting some chain frames back. That's
+ * probably unnecessary.
+ */
+ if ((sassc->flags & MPRSAS_QUEUE_FROZEN) == 0) {
+ xpt_freeze_simq(sassc->sim, 1);
+ sassc->flags |= MPRSAS_QUEUE_FROZEN;
+ mpr_dprint(sc, MPR_INFO, "Error sending command, "
+ "freezing SIM queue\n");
+ }
+ }
+
+ /*
+ * If this is a Start Stop Unit command and it was issued by the driver
+ * during shutdown, decrement the refcount to account for all of the
+ * commands that were sent. All SSU commands should be completed before
+ * shutdown completes, meaning SSU_refcount will be 0 after SSU_started
+ * is TRUE.
+ */
+ if (sc->SSU_started && (csio->cdb_io.cdb_bytes[0] == START_STOP_UNIT)) {
+ mpr_dprint(sc, MPR_INFO, "Decrementing SSU count.\n");
+ sc->SSU_refcount--;
+ }
+
+ /* Take the fast path to completion */
+ if (cm->cm_reply == NULL) {
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INPROG) {
+ if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0)
+ ccb->ccb_h.status = CAM_SCSI_BUS_RESET;
+ else {
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ }
+ if (sassc->flags & MPRSAS_QUEUE_FROZEN) {
+ ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
+ sassc->flags &= ~MPRSAS_QUEUE_FROZEN;
+ mpr_dprint(sc, MPR_XINFO,
+ "Unfreezing SIM queue\n");
+ }
+ }
+
+ /*
+ * There are two scenarios where the status won't be
+ * CAM_REQ_CMP. The first is if MPR_CM_FLAGS_ERROR_MASK is
+ * set, the second is in the MPR_FLAGS_DIAGRESET above.
+ */
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ /*
+ * Freeze the dev queue so that commands are
+ * executed in the correct order with after error
+ * recovery.
+ */
+ ccb->ccb_h.status |= CAM_DEV_QFRZN;
+ xpt_freeze_devq(ccb->ccb_h.path, /*count*/ 1);
+ }
+ mpr_free_command(sc, cm);
+ xpt_done(ccb);
+ return;
+ }
+
+ mprsas_log_command(cm, MPR_XINFO,
+ "ioc %x scsi %x state %x xfer %u\n",
+ le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState,
+ le32toh(rep->TransferCount));
+
+ switch (le16toh(rep->IOCStatus) & MPI2_IOCSTATUS_MASK) {
+ case MPI2_IOCSTATUS_SCSI_DATA_UNDERRUN:
+ csio->resid = cm->cm_length - le32toh(rep->TransferCount);
+ /* FALLTHROUGH */
+ case MPI2_IOCSTATUS_SUCCESS:
+ case MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR:
+
+ if ((le16toh(rep->IOCStatus) & MPI2_IOCSTATUS_MASK) ==
+ MPI2_IOCSTATUS_SCSI_RECOVERED_ERROR)
+ mprsas_log_command(cm, MPR_XINFO, "recovered error\n");
+
+ /* Completion failed at the transport level. */
+ if (rep->SCSIState & (MPI2_SCSI_STATE_NO_SCSI_STATUS |
+ MPI2_SCSI_STATE_TERMINATED)) {
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ break;
+ }
+
+ /* In a modern packetized environment, an autosense failure
+ * implies that there's not much else that can be done to
+ * recover the command.
+ */
+ if (rep->SCSIState & MPI2_SCSI_STATE_AUTOSENSE_FAILED) {
+ ccb->ccb_h.status = CAM_AUTOSENSE_FAIL;
+ break;
+ }
+
+ /*
+ * CAM doesn't care about SAS Response Info data, but if this is
+ * the state check if TLR should be done. If not, clear the
+ * TLR_bits for the target.
+ */
+ if ((rep->SCSIState & MPI2_SCSI_STATE_RESPONSE_INFO_VALID) &&
+ ((le32toh(rep->ResponseInfo) & MPI2_SCSI_RI_MASK_REASONCODE)
+ == MPR_SCSI_RI_INVALID_FRAME)) {
+ sc->mapping_table[csio->ccb_h.target_id].TLR_bits =
+ (u8)MPI2_SCSIIO_CONTROL_NO_TLR;
+ }
+
+ /*
+ * Intentionally override the normal SCSI status reporting
+ * for these two cases. These are likely to happen in a
+ * multi-initiator environment, and we want to make sure that
+ * CAM retries these commands rather than fail them.
+ */
+ if ((rep->SCSIStatus == MPI2_SCSI_STATUS_COMMAND_TERMINATED) ||
+ (rep->SCSIStatus == MPI2_SCSI_STATUS_TASK_ABORTED)) {
+ ccb->ccb_h.status = CAM_REQ_ABORTED;
+ break;
+ }
+
+ /* Handle normal status and sense */
+ csio->scsi_status = rep->SCSIStatus;
+ if (rep->SCSIStatus == MPI2_SCSI_STATUS_GOOD)
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ else
+ ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR;
+
+ if (rep->SCSIState & MPI2_SCSI_STATE_AUTOSENSE_VALID) {
+ int sense_len, returned_sense_len;
+
+ returned_sense_len = min(le32toh(rep->SenseCount),
+ sizeof(struct scsi_sense_data));
+ if (returned_sense_len < csio->sense_len)
+ csio->sense_resid = csio->sense_len -
+ returned_sense_len;
+ else
+ csio->sense_resid = 0;
+
+ sense_len = min(returned_sense_len,
+ csio->sense_len - csio->sense_resid);
+ bzero(&csio->sense_data, sizeof(csio->sense_data));
+ bcopy(cm->cm_sense, &csio->sense_data, sense_len);
+ ccb->ccb_h.status |= CAM_AUTOSNS_VALID;
+ }
+
+ /*
+ * Check if this is an INQUIRY command. If it's a VPD inquiry,
+ * and it's page code 0 (Supported Page List), and there is
+ * inquiry data, and this is for a sequential access device, and
+ * the device is an SSP target, and TLR is supported by the
+ * controller, turn the TLR_bits value ON if page 0x90 is
+ * supported.
+ */
+ if ((csio->cdb_io.cdb_bytes[0] == INQUIRY) &&
+ (csio->cdb_io.cdb_bytes[1] & SI_EVPD) &&
+ (csio->cdb_io.cdb_bytes[2] == SVPD_SUPPORTED_PAGE_LIST) &&
+ ((csio->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR) &&
+ (csio->data_ptr != NULL) &&
+ ((csio->data_ptr[0] & 0x1f) == T_SEQUENTIAL) &&
+ (sc->control_TLR) &&
+ (sc->mapping_table[csio->ccb_h.target_id].device_info &
+ MPI2_SAS_DEVICE_INFO_SSP_TARGET)) {
+ vpd_list = (struct scsi_vpd_supported_page_list *)
+ csio->data_ptr;
+ TLR_bits = &sc->mapping_table[csio->ccb_h.target_id].
+ TLR_bits;
+ *TLR_bits = (u8)MPI2_SCSIIO_CONTROL_NO_TLR;
+ TLR_on = (u8)MPI2_SCSIIO_CONTROL_TLR_ON;
+ alloc_len = ((u16)csio->cdb_io.cdb_bytes[3] << 8) +
+ csio->cdb_io.cdb_bytes[4];
+ alloc_len -= csio->resid;
+ for (i = 0; i < MIN(vpd_list->length, alloc_len); i++) {
+ if (vpd_list->list[i] == 0x90) {
+ *TLR_bits = TLR_on;
+ break;
+ }
+ }
+ }
+ break;
+ case MPI2_IOCSTATUS_SCSI_INVALID_DEVHANDLE:
+ case MPI2_IOCSTATUS_SCSI_DEVICE_NOT_THERE:
+ /*
+ * If devinfo is 0 this will be a volume. In that case don't
+ * tell CAM that the volume is not there. We want volumes to
+ * be enumerated until they are deleted/removed, not just
+ * failed.
+ */
+ if (cm->cm_targ->devinfo == 0)
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ else
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ break;
+ case MPI2_IOCSTATUS_INVALID_SGL:
+ mpr_print_scsiio_cmd(sc, cm);
+ ccb->ccb_h.status = CAM_UNREC_HBA_ERROR;
+ break;
+ case MPI2_IOCSTATUS_SCSI_TASK_TERMINATED:
+ /*
+ * This is one of the responses that comes back when an I/O
+ * has been aborted. If it is because of a timeout that we
+ * initiated, just set the status to CAM_CMD_TIMEOUT.
+ * Otherwise set it to CAM_REQ_ABORTED. The effect on the
+ * command is the same (it gets retried, subject to the
+ * retry counter), the only difference is what gets printed
+ * on the console.
+ */
+ if (cm->cm_state == MPR_CM_STATE_TIMEDOUT)
+ ccb->ccb_h.status = CAM_CMD_TIMEOUT;
+ else
+ ccb->ccb_h.status = CAM_REQ_ABORTED;
+ break;
+ case MPI2_IOCSTATUS_SCSI_DATA_OVERRUN:
+ /* resid is ignored for this condition */
+ csio->resid = 0;
+ ccb->ccb_h.status = CAM_DATA_RUN_ERR;
+ break;
+ case MPI2_IOCSTATUS_SCSI_IOC_TERMINATED:
+ case MPI2_IOCSTATUS_SCSI_EXT_TERMINATED:
+ /*
+ * Since these are generally external (i.e. hopefully
+ * transient transport-related) errors, retry these without
+ * decrementing the retry count.
+ */
+ ccb->ccb_h.status = CAM_REQUEUE_REQ;
+ mprsas_log_command(cm, MPR_INFO,
+ "terminated ioc %x scsi %x state %x xfer %u\n",
+ le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState,
+ le32toh(rep->TransferCount));
+ break;
+ case MPI2_IOCSTATUS_INVALID_FUNCTION:
+ case MPI2_IOCSTATUS_INTERNAL_ERROR:
+ case MPI2_IOCSTATUS_INVALID_VPID:
+ case MPI2_IOCSTATUS_INVALID_FIELD:
+ case MPI2_IOCSTATUS_INVALID_STATE:
+ case MPI2_IOCSTATUS_OP_STATE_NOT_SUPPORTED:
+ case MPI2_IOCSTATUS_SCSI_IO_DATA_ERROR:
+ case MPI2_IOCSTATUS_SCSI_PROTOCOL_ERROR:
+ case MPI2_IOCSTATUS_SCSI_RESIDUAL_MISMATCH:
+ case MPI2_IOCSTATUS_SCSI_TASK_MGMT_FAILED:
+ default:
+ mprsas_log_command(cm, MPR_XINFO,
+ "completed ioc %x scsi %x state %x xfer %u\n",
+ le16toh(rep->IOCStatus), rep->SCSIStatus, rep->SCSIState,
+ le32toh(rep->TransferCount));
+ csio->resid = cm->cm_length;
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ break;
+ }
+
+ mpr_sc_failed_io_info(sc, csio, rep, cm->cm_targ);
+
+ if (sassc->flags & MPRSAS_QUEUE_FROZEN) {
+ ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
+ sassc->flags &= ~MPRSAS_QUEUE_FROZEN;
+ mpr_dprint(sc, MPR_XINFO, "Command completed, unfreezing SIM "
+ "queue\n");
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ ccb->ccb_h.status |= CAM_DEV_QFRZN;
+ xpt_freeze_devq(ccb->ccb_h.path, /*count*/ 1);
+ }
+
+ mpr_free_command(sc, cm);
+ xpt_done(ccb);
+}
+
+#if __FreeBSD_version >= 900026
+static void
+mprsas_smpio_complete(struct mpr_softc *sc, struct mpr_command *cm)
+{
+ MPI2_SMP_PASSTHROUGH_REPLY *rpl;
+ MPI2_SMP_PASSTHROUGH_REQUEST *req;
+ uint64_t sasaddr;
+ union ccb *ccb;
+
+ ccb = cm->cm_complete_data;
+
+ /*
+ * Currently there should be no way we can hit this case. It only
+ * happens when we have a failure to allocate chain frames, and SMP
+ * commands require two S/G elements only. That should be handled
+ * in the standard request size.
+ */
+ if ((cm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ mpr_dprint(sc, MPR_ERROR,"%s: cm_flags = %#x on SMP request!\n",
+ __func__, cm->cm_flags);
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ goto bailout;
+ }
+
+ rpl = (MPI2_SMP_PASSTHROUGH_REPLY *)cm->cm_reply;
+ if (rpl == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "%s: NULL cm_reply!\n", __func__);
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ goto bailout;
+ }
+
+ req = (MPI2_SMP_PASSTHROUGH_REQUEST *)cm->cm_req;
+ sasaddr = le32toh(req->SASAddress.Low);
+ sasaddr |= ((uint64_t)(le32toh(req->SASAddress.High))) << 32;
+
+ if ((le16toh(rpl->IOCStatus) & MPI2_IOCSTATUS_MASK) !=
+ MPI2_IOCSTATUS_SUCCESS ||
+ rpl->SASStatus != MPI2_SASSTATUS_SUCCESS) {
+ mpr_dprint(sc, MPR_XINFO, "%s: IOCStatus %04x SASStatus %02x\n",
+ __func__, le16toh(rpl->IOCStatus), rpl->SASStatus);
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ goto bailout;
+ }
+
+ mpr_dprint(sc, MPR_XINFO, "%s: SMP request to SAS address "
+ "%#jx completed successfully\n", __func__, (uintmax_t)sasaddr);
+
+ if (ccb->smpio.smp_response[2] == SMP_FR_ACCEPTED)
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ else
+ ccb->ccb_h.status = CAM_SMP_STATUS_ERROR;
+
+bailout:
+ /*
+ * We sync in both directions because we had DMAs in the S/G list
+ * in both directions.
+ */
+ bus_dmamap_sync(sc->buffer_dmat, cm->cm_dmamap,
+ BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(sc->buffer_dmat, cm->cm_dmamap);
+ mpr_free_command(sc, cm);
+ xpt_done(ccb);
+}
+
+static void
+mprsas_send_smpcmd(struct mprsas_softc *sassc, union ccb *ccb,
+ uint64_t sasaddr)
+{
+ struct mpr_command *cm;
+ uint8_t *request, *response;
+ MPI2_SMP_PASSTHROUGH_REQUEST *req;
+ struct mpr_softc *sc;
+ struct sglist *sg;
+ int error;
+
+ sc = sassc->sc;
+ sg = NULL;
+ error = 0;
+
+#if (__FreeBSD_version >= 1000028) || \
+ ((__FreeBSD_version >= 902001) && (__FreeBSD_version < 1000000))
+ switch (ccb->ccb_h.flags & CAM_DATA_MASK) {
+ case CAM_DATA_PADDR:
+ case CAM_DATA_SG_PADDR:
+ /*
+ * XXX We don't yet support physical addresses here.
+ */
+ mpr_dprint(sc, MPR_ERROR, "%s: physical addresses not "
+ "supported\n", __func__);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ case CAM_DATA_SG:
+ /*
+ * The chip does not support more than one buffer for the
+ * request or response.
+ */
+ if ((ccb->smpio.smp_request_sglist_cnt > 1)
+ || (ccb->smpio.smp_response_sglist_cnt > 1)) {
+ mpr_dprint(sc, MPR_ERROR,
+ "%s: multiple request or response buffer segments "
+ "not supported for SMP\n", __func__);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ }
+
+ /*
+ * The CAM_SCATTER_VALID flag was originally implemented
+ * for the XPT_SCSI_IO CCB, which only has one data pointer.
+ * We have two. So, just take that flag to mean that we
+ * might have S/G lists, and look at the S/G segment count
+ * to figure out whether that is the case for each individual
+ * buffer.
+ */
+ if (ccb->smpio.smp_request_sglist_cnt != 0) {
+ bus_dma_segment_t *req_sg;
+
+ req_sg = (bus_dma_segment_t *)ccb->smpio.smp_request;
+ request = (uint8_t *)(uintptr_t)req_sg[0].ds_addr;
+ } else
+ request = ccb->smpio.smp_request;
+
+ if (ccb->smpio.smp_response_sglist_cnt != 0) {
+ bus_dma_segment_t *rsp_sg;
+
+ rsp_sg = (bus_dma_segment_t *)ccb->smpio.smp_response;
+ response = (uint8_t *)(uintptr_t)rsp_sg[0].ds_addr;
+ } else
+ response = ccb->smpio.smp_response;
+ break;
+ case CAM_DATA_VADDR:
+ request = ccb->smpio.smp_request;
+ response = ccb->smpio.smp_response;
+ break;
+ default:
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ }
+#else /* __FreeBSD_version < 1000028 */
+ /*
+ * XXX We don't yet support physical addresses here.
+ */
+ if (ccb->ccb_h.flags & (CAM_DATA_PHYS|CAM_SG_LIST_PHYS)) {
+ mpr_printf(sc, "%s: physical addresses not supported\n",
+ __func__);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ }
+
+ /*
+ * If the user wants to send an S/G list, check to make sure they
+ * have single buffers.
+ */
+ if (ccb->ccb_h.flags & CAM_SCATTER_VALID) {
+ /*
+ * The chip does not support more than one buffer for the
+ * request or response.
+ */
+ if ((ccb->smpio.smp_request_sglist_cnt > 1)
+ || (ccb->smpio.smp_response_sglist_cnt > 1)) {
+ mpr_dprint(sc, MPR_ERROR, "%s: multiple request or "
+ "response buffer segments not supported for SMP\n",
+ __func__);
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ }
+
+ /*
+ * The CAM_SCATTER_VALID flag was originally implemented
+ * for the XPT_SCSI_IO CCB, which only has one data pointer.
+ * We have two. So, just take that flag to mean that we
+ * might have S/G lists, and look at the S/G segment count
+ * to figure out whether that is the case for each individual
+ * buffer.
+ */
+ if (ccb->smpio.smp_request_sglist_cnt != 0) {
+ bus_dma_segment_t *req_sg;
+
+ req_sg = (bus_dma_segment_t *)ccb->smpio.smp_request;
+ request = (uint8_t *)(uintptr_t)req_sg[0].ds_addr;
+ } else
+ request = ccb->smpio.smp_request;
+
+ if (ccb->smpio.smp_response_sglist_cnt != 0) {
+ bus_dma_segment_t *rsp_sg;
+
+ rsp_sg = (bus_dma_segment_t *)ccb->smpio.smp_response;
+ response = (uint8_t *)(uintptr_t)rsp_sg[0].ds_addr;
+ } else
+ response = ccb->smpio.smp_response;
+ } else {
+ request = ccb->smpio.smp_request;
+ response = ccb->smpio.smp_response;
+ }
+#endif /* __FreeBSD_version < 1000028 */
+
+ cm = mpr_alloc_command(sc);
+ if (cm == NULL) {
+ mpr_dprint(sc, MPR_ERROR,
+ "%s: cannot allocate command\n", __func__);
+ ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
+ xpt_done(ccb);
+ return;
+ }
+
+ req = (MPI2_SMP_PASSTHROUGH_REQUEST *)cm->cm_req;
+ bzero(req, sizeof(*req));
+ req->Function = MPI2_FUNCTION_SMP_PASSTHROUGH;
+
+ /* Allow the chip to use any route to this SAS address. */
+ req->PhysicalPort = 0xff;
+
+ req->RequestDataLength = htole16(ccb->smpio.smp_request_len);
+ req->SGLFlags =
+ MPI2_SGLFLAGS_SYSTEM_ADDRESS_SPACE | MPI2_SGLFLAGS_SGL_TYPE_MPI;
+
+ mpr_dprint(sc, MPR_XINFO, "%s: sending SMP request to SAS address "
+ "%#jx\n", __func__, (uintmax_t)sasaddr);
+
+ mpr_init_sge(cm, req, &req->SGL);
+
+ /*
+ * Set up a uio to pass into mpr_map_command(). This allows us to
+ * do one map command, and one busdma call in there.
+ */
+ cm->cm_uio.uio_iov = cm->cm_iovec;
+ cm->cm_uio.uio_iovcnt = 2;
+ cm->cm_uio.uio_segflg = UIO_SYSSPACE;
+
+ /*
+ * The read/write flag isn't used by busdma, but set it just in
+ * case. This isn't exactly accurate, either, since we're going in
+ * both directions.
+ */
+ cm->cm_uio.uio_rw = UIO_WRITE;
+
+ cm->cm_iovec[0].iov_base = request;
+ cm->cm_iovec[0].iov_len = le16toh(req->RequestDataLength);
+ cm->cm_iovec[1].iov_base = response;
+ cm->cm_iovec[1].iov_len = ccb->smpio.smp_response_len;
+
+ cm->cm_uio.uio_resid = cm->cm_iovec[0].iov_len +
+ cm->cm_iovec[1].iov_len;
+
+ /*
+ * Trigger a warning message in mpr_data_cb() for the user if we
+ * wind up exceeding two S/G segments. The chip expects one
+ * segment for the request and another for the response.
+ */
+ cm->cm_max_segs = 2;
+
+ cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
+ cm->cm_complete = mprsas_smpio_complete;
+ cm->cm_complete_data = ccb;
+
+ /*
+ * Tell the mapping code that we're using a uio, and that this is
+ * an SMP passthrough request. There is a little special-case
+ * logic there (in mpr_data_cb()) to handle the bidirectional
+ * transfer.
+ */
+ cm->cm_flags |= MPR_CM_FLAGS_USE_UIO | MPR_CM_FLAGS_SMP_PASS |
+ MPR_CM_FLAGS_DATAIN | MPR_CM_FLAGS_DATAOUT;
+
+ /* The chip data format is little endian. */
+ req->SASAddress.High = htole32(sasaddr >> 32);
+ req->SASAddress.Low = htole32(sasaddr);
+
+ /*
+ * XXX Note that we don't have a timeout/abort mechanism here.
+ * From the manual, it looks like task management requests only
+ * work for SCSI IO and SATA passthrough requests. We may need to
+ * have a mechanism to retry requests in the event of a chip reset
+ * at least. Hopefully the chip will insure that any errors short
+ * of that are relayed back to the driver.
+ */
+ error = mpr_map_command(sc, cm);
+ if ((error != 0) && (error != EINPROGRESS)) {
+ mpr_dprint(sc, MPR_ERROR, "%s: error %d returned from "
+ "mpr_map_command()\n", __func__, error);
+ goto bailout_error;
+ }
+
+ return;
+
+bailout_error:
+ mpr_free_command(sc, cm);
+ ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
+ xpt_done(ccb);
+ return;
+}
+
+static void
+mprsas_action_smpio(struct mprsas_softc *sassc, union ccb *ccb)
+{
+ struct mpr_softc *sc;
+ struct mprsas_target *targ;
+ uint64_t sasaddr = 0;
+
+ sc = sassc->sc;
+
+ /*
+ * Make sure the target exists.
+ */
+ KASSERT(ccb->ccb_h.target_id < sassc->maxtargets,
+ ("Target %d out of bounds in XPT_SMP_IO\n", ccb->ccb_h.target_id));
+ targ = &sassc->targets[ccb->ccb_h.target_id];
+ if (targ->handle == 0x0) {
+ mpr_dprint(sc, MPR_ERROR, "%s: target %d does not exist!\n",
+ __func__, ccb->ccb_h.target_id);
+ ccb->ccb_h.status = CAM_SEL_TIMEOUT;
+ xpt_done(ccb);
+ return;
+ }
+
+ /*
+ * If this device has an embedded SMP target, we'll talk to it
+ * directly.
+ * figure out what the expander's address is.
+ */
+ if ((targ->devinfo & MPI2_SAS_DEVICE_INFO_SMP_TARGET) != 0)
+ sasaddr = targ->sasaddr;
+
+ /*
+ * If we don't have a SAS address for the expander yet, try
+ * grabbing it from the page 0x83 information cached in the
+ * transport layer for this target. LSI expanders report the
+ * expander SAS address as the port-associated SAS address in
+ * Inquiry VPD page 0x83. Maxim expanders don't report it in page
+ * 0x83.
+ *
+ * XXX KDM disable this for now, but leave it commented out so that
+ * it is obvious that this is another possible way to get the SAS
+ * address.
+ *
+ * The parent handle method below is a little more reliable, and
+ * the other benefit is that it works for devices other than SES
+ * devices. So you can send a SMP request to a da(4) device and it
+ * will get routed to the expander that device is attached to.
+ * (Assuming the da(4) device doesn't contain an SMP target...)
+ */
+#if 0
+ if (sasaddr == 0)
+ sasaddr = xpt_path_sas_addr(ccb->ccb_h.path);
+#endif
+
+ /*
+ * If we still don't have a SAS address for the expander, look for
+ * the parent device of this device, which is probably the expander.
+ */
+ if (sasaddr == 0) {
+#ifdef OLD_MPR_PROBE
+ struct mprsas_target *parent_target;
+#endif
+
+ if (targ->parent_handle == 0x0) {
+ mpr_dprint(sc, MPR_ERROR, "%s: handle %d does not have "
+ "a valid parent handle!\n", __func__, targ->handle);
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ goto bailout;
+ }
+#ifdef OLD_MPR_PROBE
+ parent_target = mprsas_find_target_by_handle(sassc, 0,
+ targ->parent_handle);
+
+ if (parent_target == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "%s: handle %d does not have "
+ "a valid parent target!\n", __func__, targ->handle);
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ goto bailout;
+ }
+
+ if ((parent_target->devinfo &
+ MPI2_SAS_DEVICE_INFO_SMP_TARGET) == 0) {
+ mpr_dprint(sc, MPR_ERROR, "%s: handle %d parent %d "
+ "does not have an SMP target!\n", __func__,
+ targ->handle, parent_target->handle);
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ goto bailout;
+
+ }
+
+ sasaddr = parent_target->sasaddr;
+#else /* OLD_MPR_PROBE */
+ if ((targ->parent_devinfo &
+ MPI2_SAS_DEVICE_INFO_SMP_TARGET) == 0) {
+ mpr_dprint(sc, MPR_ERROR, "%s: handle %d parent %d "
+ "does not have an SMP target!\n", __func__,
+ targ->handle, targ->parent_handle);
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ goto bailout;
+
+ }
+ if (targ->parent_sasaddr == 0x0) {
+ mpr_dprint(sc, MPR_ERROR, "%s: handle %d parent handle "
+ "%d does not have a valid SAS address!\n", __func__,
+ targ->handle, targ->parent_handle);
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ goto bailout;
+ }
+
+ sasaddr = targ->parent_sasaddr;
+#endif /* OLD_MPR_PROBE */
+
+ }
+
+ if (sasaddr == 0) {
+ mpr_dprint(sc, MPR_INFO, "%s: unable to find SAS address for "
+ "handle %d\n", __func__, targ->handle);
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ goto bailout;
+ }
+ mprsas_send_smpcmd(sassc, ccb, sasaddr);
+
+ return;
+
+bailout:
+ xpt_done(ccb);
+
+}
+#endif //__FreeBSD_version >= 900026
+
+static void
+mprsas_action_resetdev(struct mprsas_softc *sassc, union ccb *ccb)
+{
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+ struct mpr_softc *sc;
+ struct mpr_command *tm;
+ struct mprsas_target *targ;
+
+ MPR_FUNCTRACE(sassc->sc);
+ mtx_assert(&sassc->sc->mpr_mtx, MA_OWNED);
+
+ KASSERT(ccb->ccb_h.target_id < sassc->maxtargets,
+ ("Target %d out of bounds in XPT_RESET_DEV\n",
+ ccb->ccb_h.target_id));
+ sc = sassc->sc;
+ tm = mpr_alloc_command(sc);
+ if (tm == NULL) {
+ mpr_dprint(sc, MPR_ERROR,
+ "command alloc failure in mprsas_action_resetdev\n");
+ ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
+ xpt_done(ccb);
+ return;
+ }
+
+ targ = &sassc->targets[ccb->ccb_h.target_id];
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
+ req->DevHandle = htole16(targ->handle);
+ req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT;
+ req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET;
+
+ /* SAS Hard Link Reset / SATA Link Reset */
+ req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET;
+
+ tm->cm_data = NULL;
+ tm->cm_desc.HighPriority.RequestFlags =
+ MPI2_REQ_DESCRIPT_FLAGS_HIGH_PRIORITY;
+ tm->cm_complete = mprsas_resetdev_complete;
+ tm->cm_complete_data = ccb;
+ tm->cm_targ = targ;
+ mpr_map_command(sc, tm);
+}
+
+static void
+mprsas_resetdev_complete(struct mpr_softc *sc, struct mpr_command *tm)
+{
+ MPI2_SCSI_TASK_MANAGE_REPLY *resp;
+ union ccb *ccb;
+
+ MPR_FUNCTRACE(sc);
+ mtx_assert(&sc->mpr_mtx, MA_OWNED);
+
+ resp = (MPI2_SCSI_TASK_MANAGE_REPLY *)tm->cm_reply;
+ ccb = tm->cm_complete_data;
+
+ /*
+ * Currently there should be no way we can hit this case. It only
+ * happens when we have a failure to allocate chain frames, and
+ * task management commands don't have S/G lists.
+ */
+ if ((tm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ MPI2_SCSI_TASK_MANAGE_REQUEST *req;
+
+ req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
+
+ mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for reset of "
+ "handle %#04x! This should not happen!\n", __func__,
+ tm->cm_flags, req->DevHandle);
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ goto bailout;
+ }
+
+ mpr_dprint(sc, MPR_XINFO,
+ "%s: IOCStatus = 0x%x ResponseCode = 0x%x\n", __func__,
+ le16toh(resp->IOCStatus), le32toh(resp->ResponseCode));
+
+ if (le32toh(resp->ResponseCode) == MPI2_SCSITASKMGMT_RSP_TM_COMPLETE) {
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ mprsas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid,
+ CAM_LUN_WILDCARD);
+ }
+ else
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+
+bailout:
+
+ mprsas_free_tm(sc, tm);
+ xpt_done(ccb);
+}
+
+static void
+mprsas_poll(struct cam_sim *sim)
+{
+ struct mprsas_softc *sassc;
+
+ sassc = cam_sim_softc(sim);
+
+ if (sassc->sc->mpr_debug & MPR_TRACE) {
+ /* frequent debug messages during a panic just slow
+ * everything down too much.
+ */
+ mpr_printf(sassc->sc, "%s clearing MPR_TRACE\n", __func__);
+ sassc->sc->mpr_debug &= ~MPR_TRACE;
+ }
+
+ mpr_intr_locked(sassc->sc);
+}
+
+static void
+mprsas_async(void *callback_arg, uint32_t code, struct cam_path *path,
+ void *arg)
+{
+ struct mpr_softc *sc;
+
+ sc = (struct mpr_softc *)callback_arg;
+
+ switch (code) {
+#if (__FreeBSD_version >= 1000006) || \
+ ((__FreeBSD_version >= 901503) && (__FreeBSD_version < 1000000))
+ case AC_ADVINFO_CHANGED: {
+ struct mprsas_target *target;
+ struct mprsas_softc *sassc;
+ struct scsi_read_capacity_data_long rcap_buf;
+ struct ccb_dev_advinfo cdai;
+ struct mprsas_lun *lun;
+ lun_id_t lunid;
+ int found_lun;
+ uintptr_t buftype;
+
+ buftype = (uintptr_t)arg;
+
+ found_lun = 0;
+ sassc = sc->sassc;
+
+ /*
+ * We're only interested in read capacity data changes.
+ */
+ if (buftype != CDAI_TYPE_RCAPLONG)
+ break;
+
+ /*
+ * See the comment in mpr_attach_sas() for a detailed
+ * explanation. In these versions of FreeBSD we register
+ * for all events and filter out the events that don't
+ * apply to us.
+ */
+#if (__FreeBSD_version < 1000703) || \
+ ((__FreeBSD_version >= 1100000) && (__FreeBSD_version < 1100002))
+ if (xpt_path_path_id(path) != sassc->sim->path_id)
+ break;
+#endif
+
+ /*
+ * We should have a handle for this, but check to make sure.
+ */
+ KASSERT(xpt_path_target_id(path) < sassc->maxtargets,
+ ("Target %d out of bounds in mprsas_async\n",
+ xpt_path_target_id(path)));
+ target = &sassc->targets[xpt_path_target_id(path)];
+ if (target->handle == 0)
+ break;
+
+ lunid = xpt_path_lun_id(path);
+
+ SLIST_FOREACH(lun, &target->luns, lun_link) {
+ if (lun->lun_id == lunid) {
+ found_lun = 1;
+ break;
+ }
+ }
+
+ if (found_lun == 0) {
+ lun = malloc(sizeof(struct mprsas_lun), M_MPR,
+ M_NOWAIT | M_ZERO);
+ if (lun == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "Unable to alloc "
+ "LUN for EEDP support.\n");
+ break;
+ }
+ lun->lun_id = lunid;
+ SLIST_INSERT_HEAD(&target->luns, lun, lun_link);
+ }
+
+ bzero(&rcap_buf, sizeof(rcap_buf));
+ xpt_setup_ccb(&cdai.ccb_h, path, CAM_PRIORITY_NORMAL);
+ cdai.ccb_h.func_code = XPT_DEV_ADVINFO;
+ cdai.ccb_h.flags = CAM_DIR_IN;
+ cdai.buftype = CDAI_TYPE_RCAPLONG;
+ cdai.flags = 0;
+ cdai.bufsiz = sizeof(rcap_buf);
+ cdai.buf = (uint8_t *)&rcap_buf;
+ xpt_action((union ccb *)&cdai);
+ if ((cdai.ccb_h.status & CAM_DEV_QFRZN) != 0)
+ cam_release_devq(cdai.ccb_h.path, 0, 0, 0, FALSE);
+
+ if (((cdai.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
+ && (rcap_buf.prot & SRC16_PROT_EN)) {
+ lun->eedp_formatted = TRUE;
+ lun->eedp_block_size = scsi_4btoul(rcap_buf.length);
+ } else {
+ lun->eedp_formatted = FALSE;
+ lun->eedp_block_size = 0;
+ }
+ break;
+ }
+#endif
+ case AC_FOUND_DEVICE: {
+ struct ccb_getdev *cgd;
+
+ /*
+ * See the comment in mpr_attach_sas() for a detailed
+ * explanation. In these versions of FreeBSD we register
+ * for all events and filter out the events that don't
+ * apply to us.
+ */
+#if (__FreeBSD_version < 1000703) || \
+ ((__FreeBSD_version >= 1100000) && (__FreeBSD_version < 1100002))
+ if (xpt_path_path_id(path) != sc->sassc->sim->path_id)
+ break;
+#endif
+
+ cgd = arg;
+ mprsas_prepare_ssu(sc, path, cgd);
+
+#if (__FreeBSD_version < 901503) || \
+ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006))
+ mprsas_check_eedp(sc, path, cgd);
+#endif
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+mprsas_prepare_ssu(struct mpr_softc *sc, struct cam_path *path,
+ struct ccb_getdev *cgd)
+{
+ struct mprsas_softc *sassc = sc->sassc;
+ path_id_t pathid;
+ target_id_t targetid;
+ lun_id_t lunid;
+ struct mprsas_target *target;
+ struct mprsas_lun *lun;
+ uint8_t found_lun;
+
+ sassc = sc->sassc;
+ pathid = cam_sim_path(sassc->sim);
+ targetid = xpt_path_target_id(path);
+ lunid = xpt_path_lun_id(path);
+
+ KASSERT(targetid < sassc->maxtargets,
+ ("Target %d out of bounds in mprsas_prepare_ssu\n", targetid));
+ target = &sassc->targets[targetid];
+ if (target->handle == 0x0)
+ return;
+
+ /*
+ * If LUN is already in list, don't create a new one.
+ */
+ found_lun = FALSE;
+ SLIST_FOREACH(lun, &target->luns, lun_link) {
+ if (lun->lun_id == lunid) {
+ found_lun = TRUE;
+ break;
+ }
+ }
+ if (!found_lun) {
+ lun = malloc(sizeof(struct mprsas_lun), M_MPR,
+ M_NOWAIT | M_ZERO);
+ if (lun == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "Unable to alloc LUN for "
+ "preparing SSU.\n");
+ return;
+ }
+ lun->lun_id = lunid;
+ SLIST_INSERT_HEAD(&target->luns, lun, lun_link);
+ }
+
+ /*
+ * If this is a SATA direct-access end device, mark it so that a SCSI
+ * StartStopUnit command will be sent to it when the driver is being
+ * shutdown.
+ */
+ if (((cgd->inq_data.device & 0x1F) == T_DIRECT) &&
+ (target->devinfo & MPI2_SAS_DEVICE_INFO_SATA_DEVICE) &&
+ ((target->devinfo & MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) ==
+ MPI2_SAS_DEVICE_INFO_END_DEVICE)) {
+ lun->stop_at_shutdown = TRUE;
+ }
+}
+
+#if (__FreeBSD_version < 901503) || \
+ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006))
+static void
+mprsas_check_eedp(struct mpr_softc *sc, struct cam_path *path,
+ struct ccb_getdev *cgd)
+{
+ struct mprsas_softc *sassc = sc->sassc;
+ struct ccb_scsiio *csio;
+ struct scsi_read_capacity_16 *scsi_cmd;
+ struct scsi_read_capacity_eedp *rcap_buf;
+ path_id_t pathid;
+ target_id_t targetid;
+ lun_id_t lunid;
+ union ccb *ccb;
+ struct cam_path *local_path;
+ struct mprsas_target *target;
+ struct mprsas_lun *lun;
+ uint8_t found_lun;
+ char path_str[64];
+
+ sassc = sc->sassc;
+ pathid = cam_sim_path(sassc->sim);
+ targetid = xpt_path_target_id(path);
+ lunid = xpt_path_lun_id(path);
+
+ KASSERT(targetid < sassc->maxtargets,
+ ("Target %d out of bounds in mprsas_check_eedp\n", targetid));
+ target = &sassc->targets[targetid];
+ if (target->handle == 0x0)
+ return;
+
+ /*
+ * Determine if the device is EEDP capable.
+ *
+ * If this flag is set in the inquiry data, the device supports
+ * protection information, and must support the 16 byte read capacity
+ * command, otherwise continue without sending read cap 16
+ */
+ if ((cgd->inq_data.spc3_flags & SPC3_SID_PROTECT) == 0)
+ return;
+
+ /*
+ * Issue a READ CAPACITY 16 command. This info is used to determine if
+ * the LUN is formatted for EEDP support.
+ */
+ ccb = xpt_alloc_ccb_nowait();
+ if (ccb == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "Unable to alloc CCB for EEDP "
+ "support.\n");
+ return;
+ }
+
+ if (xpt_create_path(&local_path, xpt_periph, pathid, targetid, lunid)
+ != CAM_REQ_CMP) {
+ mpr_dprint(sc, MPR_ERROR, "Unable to create path for EEDP "
+ "support\n");
+ xpt_free_ccb(ccb);
+ return;
+ }
+
+ /*
+ * If LUN is already in list, don't create a new one.
+ */
+ found_lun = FALSE;
+ SLIST_FOREACH(lun, &target->luns, lun_link) {
+ if (lun->lun_id == lunid) {
+ found_lun = TRUE;
+ break;
+ }
+ }
+ if (!found_lun) {
+ lun = malloc(sizeof(struct mprsas_lun), M_MPR,
+ M_NOWAIT | M_ZERO);
+ if (lun == NULL) {
+ mpr_dprint(sc, MPR_ERROR, "Unable to alloc LUN for "
+ "EEDP support.\n");
+ xpt_free_path(local_path);
+ xpt_free_ccb(ccb);
+ return;
+ }
+ lun->lun_id = lunid;
+ SLIST_INSERT_HEAD(&target->luns, lun, lun_link);
+ }
+
+ xpt_path_string(local_path, path_str, sizeof(path_str));
+ mpr_dprint(sc, MPR_INFO, "Sending read cap: path %s handle %d\n",
+ path_str, target->handle);
+
+ /*
+ * Issue a READ CAPACITY 16 command for the LUN. The
+ * mprsas_read_cap_done function will load the read cap info into the
+ * LUN struct.
+ */
+ rcap_buf = malloc(sizeof(struct scsi_read_capacity_eedp), M_MPR,
+ M_NOWAIT | M_ZERO);
+ if (rcap_buf == NULL) {
+ mpr_dprint(sc, MPR_FAULT, "Unable to alloc read capacity "
+ "buffer for EEDP support.\n");
+ xpt_free_path(ccb->ccb_h.path);
+ xpt_free_ccb(ccb);
+ return;
+ }
+ xpt_setup_ccb(&ccb->ccb_h, local_path, CAM_PRIORITY_XPT);
+ csio = &ccb->csio;
+ csio->ccb_h.func_code = XPT_SCSI_IO;
+ csio->ccb_h.flags = CAM_DIR_IN;
+ csio->ccb_h.retry_count = 4;
+ csio->ccb_h.cbfcnp = mprsas_read_cap_done;
+ csio->ccb_h.timeout = 60000;
+ csio->data_ptr = (uint8_t *)rcap_buf;
+ csio->dxfer_len = sizeof(struct scsi_read_capacity_eedp);
+ csio->sense_len = MPR_SENSE_LEN;
+ csio->cdb_len = sizeof(*scsi_cmd);
+ csio->tag_action = MSG_SIMPLE_Q_TAG;
+
+ scsi_cmd = (struct scsi_read_capacity_16 *)&csio->cdb_io.cdb_bytes;
+ bzero(scsi_cmd, sizeof(*scsi_cmd));
+ scsi_cmd->opcode = 0x9E;
+ scsi_cmd->service_action = SRC16_SERVICE_ACTION;
+ ((uint8_t *)scsi_cmd)[13] = sizeof(struct scsi_read_capacity_eedp);
+
+ ccb->ccb_h.ppriv_ptr1 = sassc;
+ xpt_action(ccb);
+}
+
+static void
+mprsas_read_cap_done(struct cam_periph *periph, union ccb *done_ccb)
+{
+ struct mprsas_softc *sassc;
+ struct mprsas_target *target;
+ struct mprsas_lun *lun;
+ struct scsi_read_capacity_eedp *rcap_buf;
+
+ if (done_ccb == NULL)
+ return;
+
+ /* Driver need to release devq, it Scsi command is
+ * generated by driver internally.
+ * Currently there is a single place where driver
+ * calls scsi command internally. In future if driver
+ * calls more scsi command internally, it needs to release
+ * devq internally, since those command will not go back to
+ * cam_periph.
+ */
+ if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) ) {
+ done_ccb->ccb_h.status &= ~CAM_DEV_QFRZN;
+ xpt_release_devq(done_ccb->ccb_h.path,
+ /*count*/ 1, /*run_queue*/TRUE);
+ }
+
+ rcap_buf = (struct scsi_read_capacity_eedp *)done_ccb->csio.data_ptr;
+
+ /*
+ * Get the LUN ID for the path and look it up in the LUN list for the
+ * target.
+ */
+ sassc = (struct mprsas_softc *)done_ccb->ccb_h.ppriv_ptr1;
+ KASSERT(done_ccb->ccb_h.target_id < sassc->maxtargets,
+ ("Target %d out of bounds in mprsas_read_cap_done\n",
+ done_ccb->ccb_h.target_id));
+ target = &sassc->targets[done_ccb->ccb_h.target_id];
+ SLIST_FOREACH(lun, &target->luns, lun_link) {
+ if (lun->lun_id != done_ccb->ccb_h.target_lun)
+ continue;
+
+ /*
+ * Got the LUN in the target's LUN list. Fill it in with EEDP
+ * info. If the READ CAP 16 command had some SCSI error (common
+ * if command is not supported), mark the lun as not supporting
+ * EEDP and set the block size to 0.
+ */
+ if (((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)
+ || (done_ccb->csio.scsi_status != SCSI_STATUS_OK)) {
+ lun->eedp_formatted = FALSE;
+ lun->eedp_block_size = 0;
+ break;
+ }
+
+ if (rcap_buf->protect & 0x01) {
+ mpr_dprint(sassc->sc, MPR_INFO, "LUN %d for "
+ "target ID %d is formatted for EEDP "
+ "support.\n", done_ccb->ccb_h.target_lun,
+ done_ccb->ccb_h.target_id);
+ lun->eedp_formatted = TRUE;
+ lun->eedp_block_size = scsi_4btoul(rcap_buf->length);
+ }
+ break;
+ }
+
+ // Finished with this CCB and path.
+ free(rcap_buf, M_MPR);
+ xpt_free_path(done_ccb->ccb_h.path);
+ xpt_free_ccb(done_ccb);
+}
+#endif /* (__FreeBSD_version < 901503) || \
+ ((__FreeBSD_version >= 1000000) && (__FreeBSD_version < 1000006)) */
+
+int
+mprsas_startup(struct mpr_softc *sc)
+{
+ /*
+ * Send the port enable message and set the wait_for_port_enable flag.
+ * This flag helps to keep the simq frozen until all discovery events
+ * are processed.
+ */
+ sc->wait_for_port_enable = 1;
+ mprsas_send_portenable(sc);
+ return (0);
+}
+
+static int
+mprsas_send_portenable(struct mpr_softc *sc)
+{
+ MPI2_PORT_ENABLE_REQUEST *request;
+ struct mpr_command *cm;
+
+ MPR_FUNCTRACE(sc);
+
+ if ((cm = mpr_alloc_command(sc)) == NULL)
+ return (EBUSY);
+ request = (MPI2_PORT_ENABLE_REQUEST *)cm->cm_req;
+ request->Function = MPI2_FUNCTION_PORT_ENABLE;
+ request->MsgFlags = 0;
+ request->VP_ID = 0;
+ cm->cm_desc.Default.RequestFlags = MPI2_REQ_DESCRIPT_FLAGS_DEFAULT_TYPE;
+ cm->cm_complete = mprsas_portenable_complete;
+ cm->cm_data = NULL;
+ cm->cm_sge = NULL;
+
+ mpr_map_command(sc, cm);
+ mpr_dprint(sc, MPR_XINFO,
+ "mpr_send_portenable finished cm %p req %p complete %p\n",
+ cm, cm->cm_req, cm->cm_complete);
+ return (0);
+}
+
+static void
+mprsas_portenable_complete(struct mpr_softc *sc, struct mpr_command *cm)
+{
+ MPI2_PORT_ENABLE_REPLY *reply;
+ struct mprsas_softc *sassc;
+
+ MPR_FUNCTRACE(sc);
+ sassc = sc->sassc;
+
+ /*
+ * Currently there should be no way we can hit this case. It only
+ * happens when we have a failure to allocate chain frames, and
+ * port enable commands don't have S/G lists.
+ */
+ if ((cm->cm_flags & MPR_CM_FLAGS_ERROR_MASK) != 0) {
+ mpr_dprint(sc, MPR_ERROR, "%s: cm_flags = %#x for port enable! "
+ "This should not happen!\n", __func__, cm->cm_flags);
+ }
+
+ reply = (MPI2_PORT_ENABLE_REPLY *)cm->cm_reply;
+ if (reply == NULL)
+ mpr_dprint(sc, MPR_FAULT, "Portenable NULL reply\n");
+ else if (le16toh(reply->IOCStatus & MPI2_IOCSTATUS_MASK) !=
+ MPI2_IOCSTATUS_SUCCESS)
+ mpr_dprint(sc, MPR_FAULT, "Portenable failed\n");
+
+ mpr_free_command(sc, cm);
+ if (sc->mpr_ich.ich_arg != NULL) {
+ mpr_dprint(sc, MPR_XINFO, "disestablish config intrhook\n");
+ config_intrhook_disestablish(&sc->mpr_ich);
+ sc->mpr_ich.ich_arg = NULL;
+ }
+
+ /*
+ * Done waiting for port enable to complete. Decrement the refcount.
+ * If refcount is 0, discovery is complete and a rescan of the bus can
+ * take place.
+ */
+ sc->wait_for_port_enable = 0;
+ sc->port_enable_complete = 1;
+ wakeup(&sc->port_enable_complete);
+ mprsas_startup_decrement(sassc);
+}
+
+int
+mprsas_check_id(struct mprsas_softc *sassc, int id)
+{
+ struct mpr_softc *sc = sassc->sc;
+ char *ids;
+ char *name;
+
+ ids = &sc->exclude_ids[0];
+ while((name = strsep(&ids, ",")) != NULL) {
+ if (name[0] == '\0')
+ continue;
+ if (strtol(name, NULL, 0) == (long)id)
+ return (1);
+ }
+
+ return (0);
+}