71900a794d
This fixes mpr driver on big-endian devices. Tested on powerpc64 and powerpc64le targets using a SAS9300-8i card (LSISAS3008 pci vendor=0x1000 device=0x0097) Submitted by: Andre Fernando da Silva <andre.silva@eldorado.org.br> Reviewed by: luporl, alfredo, Sreekanth Reddy <sreekanth.reddy@broadcom.com> (by email) Sponsored by: Eldorado Research Institute (eldorado.org.br) MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D25785
3547 lines
104 KiB
C
3547 lines
104 KiB
C
/*-
|
|
* Copyright (c) 2009 Yahoo! Inc.
|
|
* Copyright (c) 2011-2015 LSI Corp.
|
|
* Copyright (c) 2013-2016 Avago Technologies
|
|
* Copyright 2000-2020 Broadcom Inc.
|
|
* 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.
|
|
*
|
|
* Broadcom Inc. (LSI) MPT-Fusion Host Adapter FreeBSD
|
|
*
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/* Communications core for Avago Technologies (LSI) MPT3 */
|
|
|
|
/* 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>
|
|
#include <cam/scsi/smp_all.h>
|
|
|
|
#include <dev/nvme/nvme.h>
|
|
|
|
#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_pci.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 void mprsas_async(void *callback_arg, uint32_t code,
|
|
struct cam_path *path, void *arg);
|
|
static int mprsas_send_portenable(struct mpr_softc *sc);
|
|
static void mprsas_portenable_complete(struct mpr_softc *sc,
|
|
struct mpr_command *cm);
|
|
|
|
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);
|
|
|
|
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__);
|
|
xpt_hold_boot();
|
|
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);
|
|
xpt_release_boot();
|
|
}
|
|
mpr_dprint(sassc->sc, MPR_INIT, "%s refcount %u\n", __func__,
|
|
sassc->startup_refcount);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The firmware requires us to stop sending commands when we're doing task
|
|
* management.
|
|
* use.
|
|
* XXX The logic for serializing the device has been made lazy and moved to
|
|
* mprsas_prepare_for_tm().
|
|
*/
|
|
struct mpr_command *
|
|
mprsas_alloc_tm(struct mpr_softc *sc)
|
|
{
|
|
MPI2_SCSI_TASK_MANAGE_REQUEST *req;
|
|
struct mpr_command *tm;
|
|
|
|
MPR_FUNCTRACE(sc);
|
|
tm = mpr_alloc_high_priority_command(sc);
|
|
if (tm == NULL)
|
|
return (NULL);
|
|
|
|
req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
|
|
req->Function = MPI2_FUNCTION_SCSI_TASK_MGMT;
|
|
return tm;
|
|
}
|
|
|
|
void
|
|
mprsas_free_tm(struct mpr_softc *sc, struct mpr_command *tm)
|
|
{
|
|
int target_id = 0xFFFFFFFF;
|
|
|
|
MPR_FUNCTRACE(sc);
|
|
if (tm == NULL)
|
|
return;
|
|
|
|
/*
|
|
* For TM's the devq is frozen for the device. Unfreeze it here and
|
|
* free the resources used for freezing the devq. Must clear the
|
|
* INRESET flag as well or scsi I/O will not work.
|
|
*/
|
|
if (tm->cm_targ != NULL) {
|
|
tm->cm_targ->flags &= ~MPRSAS_TARGET_INRESET;
|
|
target_id = tm->cm_targ->tid;
|
|
}
|
|
if (tm->cm_ccb) {
|
|
mpr_dprint(sc, MPR_INFO, "Unfreezing devq for target ID %d\n",
|
|
target_id);
|
|
xpt_release_devq(tm->cm_ccb->ccb_h.path, 1, TRUE);
|
|
xpt_free_path(tm->cm_ccb->ccb_h.path);
|
|
xpt_free_ccb(tm->cm_ccb);
|
|
}
|
|
|
|
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[224];
|
|
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_print_field(cm->cm_sc, "%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 ((le16toh(reply->IOCStatus) & MPI2_IOCSTATUS_MASK) !=
|
|
MPI2_IOCSTATUS_SUCCESS) {
|
|
mpr_dprint(sc, MPR_ERROR, "IOCStatus = 0x%x while resetting "
|
|
"device 0x%x\n", le16toh(reply->IOCStatus), handle);
|
|
}
|
|
|
|
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 */
|
|
|
|
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 ((le16toh(reply->IOCStatus) & MPI2_IOCSTATUS_MASK) ==
|
|
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->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET;
|
|
|
|
if (!targ->is_nvme || sc->custom_nvme_tm_handling) {
|
|
/* SAS Hard Link Reset / SATA Link Reset */
|
|
req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET;
|
|
} else {
|
|
/* PCIe Protocol Level Reset*/
|
|
req->MsgFlags =
|
|
MPI26_SCSITASKMGMT_MSGFLAGS_PROTOCOL_LVL_RST_PCIE;
|
|
}
|
|
|
|
cm->cm_targ = targ;
|
|
cm->cm_data = NULL;
|
|
cm->cm_complete = mprsas_remove_volume;
|
|
cm->cm_complete_data = (void *)(uintptr_t)handle;
|
|
|
|
mpr_dprint(sc, MPR_INFO, "%s: Sending reset for target ID %d\n",
|
|
__func__, targ->tid);
|
|
mprsas_prepare_for_tm(sc, cm, targ, CAM_LUN_WILDCARD);
|
|
|
|
mpr_map_command(sc, cm);
|
|
}
|
|
|
|
/*
|
|
* The 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 *tm;
|
|
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;
|
|
|
|
tm = mprsas_alloc_tm(sc);
|
|
if (tm == NULL) {
|
|
mpr_dprint(sc, MPR_ERROR, "%s: command alloc failure\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
mprsas_rescan_target(sc, targ);
|
|
|
|
req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
|
|
req->DevHandle = htole16(targ->handle);
|
|
req->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET;
|
|
|
|
/* SAS Hard Link Reset / SATA Link Reset */
|
|
req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET;
|
|
|
|
tm->cm_targ = targ;
|
|
tm->cm_data = NULL;
|
|
tm->cm_complete = mprsas_remove_device;
|
|
tm->cm_complete_data = (void *)(uintptr_t)handle;
|
|
|
|
mpr_dprint(sc, MPR_INFO, "%s: Sending reset for target ID %d\n",
|
|
__func__, targ->tid);
|
|
mprsas_prepare_for_tm(sc, tm, targ, CAM_LUN_WILDCARD);
|
|
|
|
mpr_map_command(sc, tm);
|
|
}
|
|
|
|
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;
|
|
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);
|
|
}
|
|
|
|
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_MASK) !=
|
|
MPI2_IOCSTATUS_SUCCESS) {
|
|
mpr_dprint(sc, MPR_ERROR, "IOCStatus = 0x%x while resetting "
|
|
"device 0x%x\n", le16toh(reply->IOCStatus), handle);
|
|
}
|
|
|
|
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;
|
|
|
|
/*
|
|
* Wait to send the REMOVE_DEVICE until all the commands have cleared.
|
|
* They should be aborted or time out and we'll kick thus off there
|
|
* if so.
|
|
*/
|
|
if (TAILQ_FIRST(&targ->commands) == NULL) {
|
|
mpr_dprint(sc, MPR_INFO, "No pending commands: starting remove_device\n");
|
|
mpr_map_command(sc, tm);
|
|
targ->pending_remove_tm = NULL;
|
|
} else {
|
|
targ->pending_remove_tm = tm;
|
|
}
|
|
|
|
mpr_dprint(sc, MPR_INFO, "clearing target %u handle 0x%04x\n",
|
|
targ->tid, handle);
|
|
if (targ->encl_level_valid) {
|
|
mpr_dprint(sc, MPR_INFO, "At enclosure level %d, slot %d, "
|
|
"connector name (%4s)\n", targ->encl_level, targ->encl_slot,
|
|
targ->connector_name);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
targ = tm->cm_targ;
|
|
|
|
/*
|
|
* At this point, we should have no pending commands for the target.
|
|
* The remove target has just completed.
|
|
*/
|
|
KASSERT(TAILQ_FIRST(&targ->commands) == NULL,
|
|
("%s: no commands should be pending\n", __func__));
|
|
|
|
/*
|
|
* 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_MASK) ==
|
|
MPI2_IOCSTATUS_SUCCESS) {
|
|
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);
|
|
setbit(events, MPI2_EVENT_SAS_DEVICE_DISCOVERY_ERROR);
|
|
if (sc->facts->MsgVersion >= MPI2_VERSION_02_06) {
|
|
setbit(events, MPI2_EVENT_ACTIVE_CABLE_EXCEPTION);
|
|
if (sc->mpr_flags & MPR_FLAGS_GEN35_IOC) {
|
|
setbit(events, MPI2_EVENT_PCIE_DEVICE_STATUS_CHANGE);
|
|
setbit(events, MPI2_EVENT_PCIE_ENUMERATION);
|
|
setbit(events, MPI2_EVENT_PCIE_TOPOLOGY_CHANGE_LIST);
|
|
}
|
|
}
|
|
|
|
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, reqs;
|
|
|
|
MPR_FUNCTRACE(sc);
|
|
mpr_dprint(sc, MPR_INIT, "%s entered\n", __func__);
|
|
|
|
sassc = malloc(sizeof(struct mprsas_softc), M_MPR, M_WAITOK|M_ZERO);
|
|
|
|
/*
|
|
* 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 + sc->facts->MaxVolumes;
|
|
sassc->targets = malloc(sizeof(struct mprsas_target) *
|
|
sassc->maxtargets, M_MPR, M_WAITOK|M_ZERO);
|
|
sc->sassc = sassc;
|
|
sassc->sc = sc;
|
|
|
|
reqs = sc->num_reqs - sc->num_prireqs - 1;
|
|
if ((sassc->devq = cam_simq_alloc(reqs)) == NULL) {
|
|
mpr_dprint(sc, MPR_INIT|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, reqs, reqs, sassc->devq);
|
|
if (sassc->sim == NULL) {
|
|
mpr_dprint(sc, MPR_INIT|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);
|
|
taskqueue_start_threads(&sassc->ev_tq, 1, PRIBIO, "%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_INIT|MPR_ERROR,
|
|
"Error %d registering SCSI bus\n", error);
|
|
mpr_unlock(sc);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Assume that discovery events will start right away.
|
|
*
|
|
* 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 /*mpsafe*/);
|
|
|
|
/*
|
|
* 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_dprint(sc, MPR_INIT|MPR_ERROR,
|
|
"Error %#x creating sim path\n", status);
|
|
sassc->path = NULL;
|
|
} else {
|
|
int event;
|
|
|
|
event = AC_ADVINFO_CHANGED | AC_FOUND_DEVICE;
|
|
status = xpt_register_async(event, mprsas_async, sc,
|
|
sassc->path);
|
|
|
|
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);
|
|
|
|
mpr_dprint(sc, MPR_INIT, "%s exit, error= %d\n", __func__, error);
|
|
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);
|
|
|
|
while (sassc->startup_refcount != 0)
|
|
mprsas_startup_decrement(sassc);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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);
|
|
|
|
/*
|
|
* After discovery has completed, check the mapping table for any
|
|
* missing devices and update their missing counts. Only do this once
|
|
* whenever the driver is initialized so that missing counts aren't
|
|
* updated unnecessarily. Note that just because discovery has
|
|
* completed doesn't mean that events have been processed yet. The
|
|
* check_devices function is a callout timer that checks if ALL devices
|
|
* are missing. If so, it will wait a little longer for events to
|
|
* complete and keep resetting itself until some device in the mapping
|
|
* table is not missing, meaning that event processing has started.
|
|
*/
|
|
if (sc->track_mapping_events) {
|
|
mpr_dprint(sc, MPR_XINFO | MPR_MAPPING, "Discovery has "
|
|
"completed. Check for missing devices in the mapping "
|
|
"table.\n");
|
|
callout_reset(&sc->device_check_callout,
|
|
MPR_MISSING_CHECK_DELAY * hz, mpr_mapping_check_devices,
|
|
sc);
|
|
}
|
|
}
|
|
|
|
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, "ccb func_code 0x%x\n",
|
|
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;
|
|
struct mpr_softc *sc = sassc->sc;
|
|
|
|
cpi->version_num = 1;
|
|
cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16;
|
|
cpi->target_sprt = 0;
|
|
cpi->hba_misc = PIM_NOBUSRESET | PIM_UNMAPPED | PIM_NOSCAN;
|
|
cpi->hba_eng_cnt = 0;
|
|
cpi->max_target = sassc->maxtargets - 1;
|
|
cpi->max_lun = 255;
|
|
|
|
/*
|
|
* initiator_id is set here to an ID outside the set of valid
|
|
* target IDs (including volumes).
|
|
*/
|
|
cpi->initiator_id = sassc->maxtargets;
|
|
strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
|
|
strlcpy(cpi->hba_vid, "Avago Tech", HBA_IDLEN);
|
|
strlcpy(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;
|
|
cpi->maxio = sc->maxio;
|
|
mprsas_set_ccbstatus(ccb, 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) {
|
|
mprsas_set_ccbstatus(ccb, 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;
|
|
case 0x0b:
|
|
sas->bitrate = 1200000;
|
|
break;
|
|
default:
|
|
sas->valid = 0;
|
|
}
|
|
|
|
cts->protocol = PROTO_SCSI;
|
|
scsi->valid = CTS_SCSI_VALID_TQ;
|
|
scsi->flags = CTS_SCSI_FLAGS_TAG_ENB;
|
|
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
break;
|
|
}
|
|
case XPT_CALC_GEOMETRY:
|
|
cam_calc_geometry(&ccb->ccg, /*extended*/1);
|
|
mprsas_set_ccbstatus(ccb, 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");
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
break;
|
|
case XPT_SCSI_IO:
|
|
mprsas_action_scsiio(sassc, ccb);
|
|
return;
|
|
case XPT_SMP_IO:
|
|
mprsas_action_smpio(sassc, ccb);
|
|
return;
|
|
default:
|
|
mprsas_set_ccbstatus(ccb, 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];
|
|
if (cm->cm_state == MPR_CM_STATE_FREE)
|
|
continue;
|
|
|
|
cm->cm_state = MPR_CM_STATE_BUSY;
|
|
cm->cm_reply = NULL;
|
|
completed = 0;
|
|
|
|
if (cm->cm_flags & MPR_CM_FLAGS_SATA_ID_TIMEOUT) {
|
|
MPASS(cm->cm_data);
|
|
free(cm->cm_data, M_MPR);
|
|
cm->cm_data = NULL;
|
|
}
|
|
|
|
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;
|
|
} else 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);
|
|
}
|
|
}
|
|
|
|
sc->io_cmds_active = 0;
|
|
}
|
|
|
|
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 after command completion\n",
|
|
__func__, sc->sassc->startup_refcount);
|
|
|
|
/* 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);
|
|
|
|
KASSERT(tm->cm_state == MPR_CM_STATE_INQUEUE,
|
|
("command not inqueue\n"));
|
|
|
|
tm->cm_state = MPR_CM_STATE_BUSY;
|
|
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_RECOVERY|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) {
|
|
mpr_dprint(sc, 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 */
|
|
mpr_dprint(sc, MPR_RECOVERY, "Hardware undergoing "
|
|
"reset, ignoring NULL LUN reset reply\n");
|
|
targ->tm = NULL;
|
|
mprsas_free_tm(sc, tm);
|
|
}
|
|
else {
|
|
/* we should have gotten a reply. */
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY, "NULL reply on "
|
|
"LUN reset attempt, resetting controller\n");
|
|
mpr_reinit(sc);
|
|
}
|
|
return;
|
|
}
|
|
|
|
mpr_dprint(sc, 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) {
|
|
mpr_dprint(sc, MPR_RECOVERY|MPR_INFO,
|
|
"Finished recovery after LUN reset for target %u\n",
|
|
targ->tid);
|
|
|
|
mprsas_announce_reset(sc, AC_SENT_BDR, 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) {
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY,
|
|
"More commands to abort for target %u\n", targ->tid);
|
|
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.
|
|
*/
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY,
|
|
"logical unit reset complete for target %u, but still "
|
|
"have %u command(s), sending target reset\n", targ->tid,
|
|
cm_count);
|
|
if (!targ->is_nvme || sc->custom_nvme_tm_handling)
|
|
mprsas_send_reset(sc, tm,
|
|
MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET);
|
|
else
|
|
mpr_reinit(sc);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
mpr_dprint(sc, MPR_RECOVERY,
|
|
"NULL target reset 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 */
|
|
mpr_dprint(sc, MPR_RECOVERY, "Hardware undergoing "
|
|
"reset, ignoring NULL target reset reply\n");
|
|
targ->tm = NULL;
|
|
mprsas_free_tm(sc, tm);
|
|
}
|
|
else {
|
|
/* we should have gotten a reply. */
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY, "NULL reply on "
|
|
"target reset attempt, resetting controller\n");
|
|
mpr_reinit(sc);
|
|
}
|
|
return;
|
|
}
|
|
|
|
mpr_dprint(sc, MPR_RECOVERY,
|
|
"target reset status 0x%x code 0x%x count %u\n",
|
|
le16toh(reply->IOCStatus), le32toh(reply->ResponseCode),
|
|
le32toh(reply->TerminationCount));
|
|
|
|
if (targ->outstanding == 0) {
|
|
/*
|
|
* We've finished recovery for this target and all
|
|
* of its logical units.
|
|
*/
|
|
mpr_dprint(sc, MPR_RECOVERY|MPR_INFO,
|
|
"Finished reset recovery for target %u\n", targ->tid);
|
|
|
|
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.
|
|
*/
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY,
|
|
"Target reset complete for target %u, but still have %u "
|
|
"command(s), resetting controller\n", targ->tid,
|
|
targ->outstanding);
|
|
mpr_reinit(sc);
|
|
}
|
|
}
|
|
|
|
#define MPR_RESET_TIMEOUT 30
|
|
|
|
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, timeout;
|
|
|
|
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->TaskType = type;
|
|
|
|
if (!target->is_nvme || sc->custom_nvme_tm_handling) {
|
|
timeout = MPR_RESET_TIMEOUT;
|
|
/*
|
|
* Target reset method =
|
|
* SAS Hard Link Reset / SATA Link Reset
|
|
*/
|
|
req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET;
|
|
} else {
|
|
timeout = (target->controller_reset_timeout) ? (
|
|
target->controller_reset_timeout) : (MPR_RESET_TIMEOUT);
|
|
/* PCIe Protocol Level Reset*/
|
|
req->MsgFlags =
|
|
MPI26_SCSITASKMGMT_MSGFLAGS_PROTOCOL_LVL_RST_PCIE;
|
|
}
|
|
|
|
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++;
|
|
mpr_dprint(sc, MPR_RECOVERY|MPR_INFO,
|
|
"Sending logical unit reset to target %u lun %d\n",
|
|
target->tid, tm->cm_lun);
|
|
tm->cm_complete = mprsas_logical_unit_reset_complete;
|
|
mprsas_prepare_for_tm(sc, tm, target, tm->cm_lun);
|
|
} else if (type == MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET) {
|
|
tm->cm_targ->target_resets++;
|
|
mpr_dprint(sc, MPR_RECOVERY|MPR_INFO,
|
|
"Sending target reset to target %u\n", target->tid);
|
|
tm->cm_complete = mprsas_target_reset_complete;
|
|
mprsas_prepare_for_tm(sc, tm, target, CAM_LUN_WILDCARD);
|
|
}
|
|
else {
|
|
mpr_dprint(sc, MPR_ERROR, "unexpected reset type 0x%x\n", type);
|
|
return -1;
|
|
}
|
|
|
|
if (target->encl_level_valid) {
|
|
mpr_dprint(sc, MPR_RECOVERY|MPR_INFO,
|
|
"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_complete_data = (void *)tm;
|
|
|
|
callout_reset(&tm->cm_callout, timeout * hz,
|
|
mprsas_tm_timeout, tm);
|
|
|
|
err = mpr_map_command(sc, tm);
|
|
if (err)
|
|
mpr_dprint(sc, MPR_ERROR|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) {
|
|
mpr_dprint(sc, MPR_RECOVERY|MPR_ERROR,
|
|
"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) {
|
|
mpr_dprint(sc, 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 */
|
|
mpr_dprint(sc, MPR_RECOVERY, "Hardware undergoing "
|
|
"reset, ignoring NULL abort reply\n");
|
|
targ->tm = NULL;
|
|
mprsas_free_tm(sc, tm);
|
|
} else {
|
|
/* we should have gotten a reply. */
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY, "NULL reply on "
|
|
"abort attempt, resetting controller\n");
|
|
mpr_reinit(sc);
|
|
}
|
|
return;
|
|
}
|
|
|
|
mpr_dprint(sc, 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.
|
|
*/
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY,
|
|
"Finished abort recovery for target %u\n", targ->tid);
|
|
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 */
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY,
|
|
"Continuing abort recovery for target %u\n", targ->tid);
|
|
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.
|
|
*/
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY,
|
|
"Abort failed for target %u, sending logical unit reset\n",
|
|
targ->tid);
|
|
|
|
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, timeout;
|
|
|
|
targ = cm->cm_targ;
|
|
if (targ->handle == 0) {
|
|
mpr_dprint(sc, MPR_ERROR|MPR_RECOVERY,
|
|
"%s null devhandle for target_id %d\n",
|
|
__func__, cm->cm_ccb->ccb_h.target_id);
|
|
return -1;
|
|
}
|
|
|
|
mprsas_log_command(cm, MPR_RECOVERY|MPR_INFO,
|
|
"Aborting command %p\n", cm);
|
|
|
|
req = (MPI2_SCSI_TASK_MANAGE_REQUEST *)tm->cm_req;
|
|
req->DevHandle = htole16(targ->handle);
|
|
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_complete = mprsas_abort_complete;
|
|
tm->cm_complete_data = (void *)tm;
|
|
tm->cm_targ = cm->cm_targ;
|
|
tm->cm_lun = cm->cm_lun;
|
|
|
|
if (!targ->is_nvme || sc->custom_nvme_tm_handling)
|
|
timeout = MPR_ABORT_TIMEOUT;
|
|
else
|
|
timeout = sc->nvme_abort_timeout;
|
|
|
|
callout_reset(&tm->cm_callout, timeout * hz,
|
|
mprsas_tm_timeout, tm);
|
|
|
|
targ->aborts++;
|
|
|
|
mprsas_prepare_for_tm(sc, tm, targ, tm->cm_lun);
|
|
|
|
err = mpr_map_command(sc, tm);
|
|
if (err)
|
|
mpr_dprint(sc, MPR_ERROR|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)
|
|
{
|
|
sbintime_t elapsed, now;
|
|
union ccb *ccb;
|
|
struct mpr_softc *sc;
|
|
struct mpr_command *cm;
|
|
struct mprsas_target *targ;
|
|
|
|
cm = (struct mpr_command *)data;
|
|
sc = cm->cm_sc;
|
|
ccb = cm->cm_ccb;
|
|
now = sbinuptime();
|
|
|
|
MPR_FUNCTRACE(sc);
|
|
mtx_assert(&sc->mpr_mtx, MA_OWNED);
|
|
|
|
mpr_dprint(sc, MPR_XINFO|MPR_RECOVERY, "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_flags & MPR_CM_FLAGS_ON_RECOVERY) {
|
|
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++;
|
|
|
|
elapsed = now - ccb->ccb_h.qos.sim_data;
|
|
mprsas_log_command(cm, MPR_INFO|MPR_RECOVERY,
|
|
"Command timeout on target %u(0x%04x), %d set, %d.%d elapsed\n",
|
|
targ->tid, targ->handle, ccb->ccb_h.timeout,
|
|
sbintime_getsec(elapsed), elapsed & 0xffffffff);
|
|
if (targ->encl_level_valid) {
|
|
mpr_dprint(sc, MPR_INFO|MPR_RECOVERY,
|
|
"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.
|
|
*/
|
|
mprsas_set_ccbstatus(cm->cm_ccb, CAM_CMD_TIMEOUT);
|
|
cm->cm_flags |= MPR_CM_FLAGS_ON_RECOVERY | MPR_CM_FLAGS_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) {
|
|
/* start recovery by aborting the first timedout command */
|
|
mpr_dprint(sc, MPR_RECOVERY|MPR_INFO,
|
|
"Sending abort to target %u for SMID %d\n", targ->tid,
|
|
cm->cm_desc.Default.SMID);
|
|
mpr_dprint(sc, MPR_RECOVERY, "timedout cm %p allocated tm %p\n",
|
|
cm, targ->tm);
|
|
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_ERROR|MPR_RECOVERY,
|
|
"timedout cm %p failed to allocate a tm\n", cm);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* mprsas_build_nvme_unmap - Build Native NVMe DSM command equivalent
|
|
* to SCSI Unmap.
|
|
* Return 0 - for success,
|
|
* 1 - to immediately return back the command with success status to CAM
|
|
* negative value - to fallback to firmware path i.e. issue scsi unmap
|
|
* to FW without any translation.
|
|
*/
|
|
static int
|
|
mprsas_build_nvme_unmap(struct mpr_softc *sc, struct mpr_command *cm,
|
|
union ccb *ccb, struct mprsas_target *targ)
|
|
{
|
|
Mpi26NVMeEncapsulatedRequest_t *req = NULL;
|
|
struct ccb_scsiio *csio;
|
|
struct unmap_parm_list *plist;
|
|
struct nvme_dsm_range *nvme_dsm_ranges = NULL;
|
|
struct nvme_command *c;
|
|
int i, res;
|
|
uint16_t ndesc, list_len, data_length;
|
|
struct mpr_prp_page *prp_page_info;
|
|
uint64_t nvme_dsm_ranges_dma_handle;
|
|
|
|
csio = &ccb->csio;
|
|
list_len = (scsiio_cdb_ptr(csio)[7] << 8 | scsiio_cdb_ptr(csio)[8]);
|
|
if (!list_len) {
|
|
mpr_dprint(sc, MPR_ERROR, "Parameter list length is Zero\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
plist = malloc(csio->dxfer_len, M_MPR, M_ZERO|M_NOWAIT);
|
|
if (!plist) {
|
|
mpr_dprint(sc, MPR_ERROR, "Unable to allocate memory to "
|
|
"save UNMAP data\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Copy SCSI unmap data to a local buffer */
|
|
bcopy(csio->data_ptr, plist, csio->dxfer_len);
|
|
|
|
/* return back the unmap command to CAM with success status,
|
|
* if number of descripts is zero.
|
|
*/
|
|
ndesc = be16toh(plist->unmap_blk_desc_data_len) >> 4;
|
|
if (!ndesc) {
|
|
mpr_dprint(sc, MPR_XINFO, "Number of descriptors in "
|
|
"UNMAP cmd is Zero\n");
|
|
res = 1;
|
|
goto out;
|
|
}
|
|
|
|
data_length = ndesc * sizeof(struct nvme_dsm_range);
|
|
if (data_length > targ->MDTS) {
|
|
mpr_dprint(sc, MPR_ERROR, "data length: %d is greater than "
|
|
"Device's MDTS: %d\n", data_length, targ->MDTS);
|
|
res = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
prp_page_info = mpr_alloc_prp_page(sc);
|
|
KASSERT(prp_page_info != NULL, ("%s: There is no PRP Page for "
|
|
"UNMAP command.\n", __func__));
|
|
|
|
/*
|
|
* Insert the allocated PRP page into the command's PRP page list. This
|
|
* will be freed when the command is freed.
|
|
*/
|
|
TAILQ_INSERT_TAIL(&cm->cm_prp_page_list, prp_page_info, prp_page_link);
|
|
|
|
nvme_dsm_ranges = (struct nvme_dsm_range *)prp_page_info->prp_page;
|
|
nvme_dsm_ranges_dma_handle = prp_page_info->prp_page_busaddr;
|
|
|
|
bzero(nvme_dsm_ranges, data_length);
|
|
|
|
/* Convert SCSI unmap's descriptor data to NVMe DSM specific Range data
|
|
* for each descriptors contained in SCSI UNMAP data.
|
|
*/
|
|
for (i = 0; i < ndesc; i++) {
|
|
nvme_dsm_ranges[i].length =
|
|
htole32(be32toh(plist->desc[i].nlb));
|
|
nvme_dsm_ranges[i].starting_lba =
|
|
htole64(be64toh(plist->desc[i].slba));
|
|
nvme_dsm_ranges[i].attributes = 0;
|
|
}
|
|
|
|
/* Build MPI2.6's NVMe Encapsulated Request Message */
|
|
req = (Mpi26NVMeEncapsulatedRequest_t *)cm->cm_req;
|
|
bzero(req, sizeof(*req));
|
|
req->DevHandle = htole16(targ->handle);
|
|
req->Function = MPI2_FUNCTION_NVME_ENCAPSULATED;
|
|
req->Flags = MPI26_NVME_FLAGS_WRITE;
|
|
req->ErrorResponseBaseAddress.High =
|
|
htole32((uint32_t)((uint64_t)cm->cm_sense_busaddr >> 32));
|
|
req->ErrorResponseBaseAddress.Low =
|
|
htole32(cm->cm_sense_busaddr);
|
|
req->ErrorResponseAllocationLength =
|
|
htole16(sizeof(struct nvme_completion));
|
|
req->EncapsulatedCommandLength =
|
|
htole16(sizeof(struct nvme_command));
|
|
req->DataLength = htole32(data_length);
|
|
|
|
/* Build NVMe DSM command */
|
|
c = (struct nvme_command *) req->NVMe_Command;
|
|
c->opc = NVME_OPC_DATASET_MANAGEMENT;
|
|
c->nsid = htole32(csio->ccb_h.target_lun + 1);
|
|
c->cdw10 = htole32(ndesc - 1);
|
|
c->cdw11 = htole32(NVME_DSM_ATTR_DEALLOCATE);
|
|
|
|
cm->cm_length = data_length;
|
|
cm->cm_data = NULL;
|
|
|
|
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;
|
|
|
|
cm->cm_desc.Default.RequestFlags =
|
|
MPI26_REQ_DESCRIPT_FLAGS_PCIE_ENCAPSULATED;
|
|
|
|
csio->ccb_h.qos.sim_data = sbinuptime();
|
|
callout_reset_sbt(&cm->cm_callout, SBT_1MS * ccb->ccb_h.timeout, 0,
|
|
mprsas_scsiio_timeout, cm, 0);
|
|
|
|
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_build_nvme_prp(sc, cm, req,
|
|
(void *)(uintptr_t)nvme_dsm_ranges_dma_handle, 0, data_length);
|
|
mpr_map_command(sc, cm);
|
|
|
|
out:
|
|
free(plist, M_MPR);
|
|
return 0;
|
|
}
|
|
|
|
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, scsi_opcode;
|
|
uint16_t eedp_flags;
|
|
uint32_t mpi_control;
|
|
int rc;
|
|
|
|
sc = sassc->sc;
|
|
MPR_FUNCTRACE(sc);
|
|
mtx_assert(&sc->mpr_mtx, MA_OWNED);
|
|
|
|
csio = &ccb->csio;
|
|
KASSERT(csio->ccb_h.target_id < sassc->maxtargets,
|
|
("Target %d out of bounds in XPT_SCSI_IO\n",
|
|
csio->ccb_h.target_id));
|
|
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);
|
|
mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE);
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
if (targ->flags & MPR_TARGET_FLAGS_RAID_COMPONENT) {
|
|
mpr_dprint(sc, MPR_ERROR, "%s Raid component no SCSI IO "
|
|
"supported %u\n", __func__, csio->ccb_h.target_id);
|
|
mprsas_set_ccbstatus(ccb, 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 (mprsas_get_ccbstatus(ccb) != 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. In either event,
|
|
* we're removing the target due to a firmware event telling us
|
|
* the device is now gone (as opposed to some transient event). Since
|
|
* we're opting to remove failed devices from the OS's view, we need
|
|
* to propagate that status up the stack.
|
|
*/
|
|
if (targ->flags & MPRSAS_TARGET_INREMOVAL) {
|
|
if (targ->devinfo == 0)
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
else
|
|
mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE);
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
|
|
if ((sc->mpr_flags & MPR_FLAGS_SHUTDOWN) != 0) {
|
|
mpr_dprint(sc, MPR_INFO, "%s shutting down\n", __func__);
|
|
mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE);
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If target has a reset in progress, freeze the devq and return. The
|
|
* devq will be released when the TM reset is finished.
|
|
*/
|
|
if (targ->flags & MPRSAS_TARGET_INRESET) {
|
|
ccb->ccb_h.status = CAM_BUSY | CAM_DEV_QFRZN;
|
|
mpr_dprint(sc, MPR_INFO, "%s: Freezing devq for target ID %d\n",
|
|
__func__, targ->tid);
|
|
xpt_freeze_devq(ccb->ccb_h.path, 1);
|
|
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;
|
|
}
|
|
|
|
/* For NVME device's issue UNMAP command directly to NVME drives by
|
|
* constructing equivalent native NVMe DataSetManagement command.
|
|
*/
|
|
scsi_opcode = scsiio_cdb_ptr(csio)[0];
|
|
if (scsi_opcode == UNMAP &&
|
|
targ->is_nvme &&
|
|
(csio->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR) {
|
|
rc = mprsas_build_nvme_unmap(sc, cm, ccb, targ);
|
|
if (rc == 1) { /* return command to CAM with success status */
|
|
mpr_free_command(sc, cm);
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
xpt_done(ccb);
|
|
return;
|
|
} else if (!rc) /* Issued NVMe Encapsulated Request Message */
|
|
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 |= (csio->priority << MPI2_SCSIIO_CONTROL_CMDPRI_SHIFT) &
|
|
MPI2_SCSIIO_CONTROL_CMDPRI_MASK;
|
|
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);
|
|
mprsas_set_ccbstatus(ccb, 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 {
|
|
KASSERT(csio->cdb_len <= IOCDBLEN,
|
|
("cdb_len %d is greater than IOCDBLEN but CAM_CDB_POINTER "
|
|
"is not set", csio->cdb_len));
|
|
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 = htole32(lun->eedp_block_size);
|
|
eedp_flags |= (MPI2_SCSIIO_EEDPFLAGS_INC_PRI_REFTAG |
|
|
MPI2_SCSIIO_EEDPFLAGS_CHECK_REFTAG |
|
|
MPI2_SCSIIO_EEDPFLAGS_CHECK_GUARD);
|
|
if (sc->mpr_flags & MPR_FLAGS_GEN35_IOC) {
|
|
eedp_flags |=
|
|
MPI25_SCSIIO_EEDPFLAGS_APPTAG_DISABLE_MODE;
|
|
}
|
|
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;
|
|
if (!sc->atomic_desc_capable) {
|
|
cm->cm_desc.FastPathSCSIIO.DevHandle =
|
|
htole16(targ->handle);
|
|
}
|
|
} else {
|
|
cm->cm_desc.SCSIIO.RequestFlags =
|
|
MPI2_REQ_DESCRIPT_FLAGS_SCSI_IO;
|
|
if (!sc->atomic_desc_capable)
|
|
cm->cm_desc.SCSIIO.DevHandle = htole16(targ->handle);
|
|
}
|
|
|
|
csio->ccb_h.qos.sim_data = sbinuptime();
|
|
callout_reset_sbt(&cm->cm_callout, SBT_1MS * ccb->ccb_h.timeout, 0,
|
|
mprsas_scsiio_timeout, cm, 0);
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
u32 log_info = le32toh(mpi_reply->IOCLogInfo);
|
|
|
|
if (log_info == 0x31170000)
|
|
return;
|
|
|
|
desc_ioc_state = mpr_describe_table(mpr_iocstatus_string,
|
|
ioc_status);
|
|
desc_scsi_status = mpr_describe_table(mpr_scsi_status_string,
|
|
scsi_status);
|
|
|
|
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 %b\n", desc_scsi_status, scsi_status,
|
|
scsi_state, "\20" "\1AutosenseValid" "\2AutosenseFailed"
|
|
"\3NoScsiStatus" "\4Terminated" "\5Response InfoValid");
|
|
|
|
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_dprint(sc, MPR_XINFO, "response code(0x%01x): %s\n",
|
|
response_bytes[0],
|
|
mpr_describe_table(mpr_scsi_taskmgmt_string,
|
|
response_bytes[0]));
|
|
}
|
|
}
|
|
|
|
/** mprsas_nvme_trans_status_code
|
|
*
|
|
* Convert Native NVMe command error status to
|
|
* equivalent SCSI error status.
|
|
*
|
|
* Returns appropriate scsi_status
|
|
*/
|
|
static u8
|
|
mprsas_nvme_trans_status_code(uint16_t nvme_status,
|
|
struct mpr_command *cm)
|
|
{
|
|
u8 status = MPI2_SCSI_STATUS_GOOD;
|
|
int skey, asc, ascq;
|
|
union ccb *ccb = cm->cm_complete_data;
|
|
int returned_sense_len;
|
|
uint8_t sct, sc;
|
|
|
|
sct = NVME_STATUS_GET_SCT(nvme_status);
|
|
sc = NVME_STATUS_GET_SC(nvme_status);
|
|
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_ILLEGAL_REQUEST;
|
|
asc = SCSI_ASC_NO_SENSE;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
|
|
switch (sct) {
|
|
case NVME_SCT_GENERIC:
|
|
switch (sc) {
|
|
case NVME_SC_SUCCESS:
|
|
status = MPI2_SCSI_STATUS_GOOD;
|
|
skey = SSD_KEY_NO_SENSE;
|
|
asc = SCSI_ASC_NO_SENSE;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_INVALID_OPCODE:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_ILLEGAL_REQUEST;
|
|
asc = SCSI_ASC_ILLEGAL_COMMAND;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_INVALID_FIELD:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_ILLEGAL_REQUEST;
|
|
asc = SCSI_ASC_INVALID_CDB;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_DATA_TRANSFER_ERROR:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_MEDIUM_ERROR;
|
|
asc = SCSI_ASC_NO_SENSE;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_ABORTED_POWER_LOSS:
|
|
status = MPI2_SCSI_STATUS_TASK_ABORTED;
|
|
skey = SSD_KEY_ABORTED_COMMAND;
|
|
asc = SCSI_ASC_WARNING;
|
|
ascq = SCSI_ASCQ_POWER_LOSS_EXPECTED;
|
|
break;
|
|
case NVME_SC_INTERNAL_DEVICE_ERROR:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_HARDWARE_ERROR;
|
|
asc = SCSI_ASC_INTERNAL_TARGET_FAILURE;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_ABORTED_BY_REQUEST:
|
|
case NVME_SC_ABORTED_SQ_DELETION:
|
|
case NVME_SC_ABORTED_FAILED_FUSED:
|
|
case NVME_SC_ABORTED_MISSING_FUSED:
|
|
status = MPI2_SCSI_STATUS_TASK_ABORTED;
|
|
skey = SSD_KEY_ABORTED_COMMAND;
|
|
asc = SCSI_ASC_NO_SENSE;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_INVALID_NAMESPACE_OR_FORMAT:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_ILLEGAL_REQUEST;
|
|
asc = SCSI_ASC_ACCESS_DENIED_INVALID_LUN_ID;
|
|
ascq = SCSI_ASCQ_INVALID_LUN_ID;
|
|
break;
|
|
case NVME_SC_LBA_OUT_OF_RANGE:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_ILLEGAL_REQUEST;
|
|
asc = SCSI_ASC_ILLEGAL_BLOCK;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_CAPACITY_EXCEEDED:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_MEDIUM_ERROR;
|
|
asc = SCSI_ASC_NO_SENSE;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_NAMESPACE_NOT_READY:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_NOT_READY;
|
|
asc = SCSI_ASC_LUN_NOT_READY;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
}
|
|
break;
|
|
case NVME_SCT_COMMAND_SPECIFIC:
|
|
switch (sc) {
|
|
case NVME_SC_INVALID_FORMAT:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_ILLEGAL_REQUEST;
|
|
asc = SCSI_ASC_FORMAT_COMMAND_FAILED;
|
|
ascq = SCSI_ASCQ_FORMAT_COMMAND_FAILED;
|
|
break;
|
|
case NVME_SC_CONFLICTING_ATTRIBUTES:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_ILLEGAL_REQUEST;
|
|
asc = SCSI_ASC_INVALID_CDB;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
}
|
|
break;
|
|
case NVME_SCT_MEDIA_ERROR:
|
|
switch (sc) {
|
|
case NVME_SC_WRITE_FAULTS:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_MEDIUM_ERROR;
|
|
asc = SCSI_ASC_PERIPHERAL_DEV_WRITE_FAULT;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_UNRECOVERED_READ_ERROR:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_MEDIUM_ERROR;
|
|
asc = SCSI_ASC_UNRECOVERED_READ_ERROR;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_GUARD_CHECK_ERROR:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_MEDIUM_ERROR;
|
|
asc = SCSI_ASC_LOG_BLOCK_GUARD_CHECK_FAILED;
|
|
ascq = SCSI_ASCQ_LOG_BLOCK_GUARD_CHECK_FAILED;
|
|
break;
|
|
case NVME_SC_APPLICATION_TAG_CHECK_ERROR:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_MEDIUM_ERROR;
|
|
asc = SCSI_ASC_LOG_BLOCK_APPTAG_CHECK_FAILED;
|
|
ascq = SCSI_ASCQ_LOG_BLOCK_APPTAG_CHECK_FAILED;
|
|
break;
|
|
case NVME_SC_REFERENCE_TAG_CHECK_ERROR:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_MEDIUM_ERROR;
|
|
asc = SCSI_ASC_LOG_BLOCK_REFTAG_CHECK_FAILED;
|
|
ascq = SCSI_ASCQ_LOG_BLOCK_REFTAG_CHECK_FAILED;
|
|
break;
|
|
case NVME_SC_COMPARE_FAILURE:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_MISCOMPARE;
|
|
asc = SCSI_ASC_MISCOMPARE_DURING_VERIFY;
|
|
ascq = SCSI_ASCQ_CAUSE_NOT_REPORTABLE;
|
|
break;
|
|
case NVME_SC_ACCESS_DENIED:
|
|
status = MPI2_SCSI_STATUS_CHECK_CONDITION;
|
|
skey = SSD_KEY_ILLEGAL_REQUEST;
|
|
asc = SCSI_ASC_ACCESS_DENIED_INVALID_LUN_ID;
|
|
ascq = SCSI_ASCQ_INVALID_LUN_ID;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
returned_sense_len = sizeof(struct scsi_sense_data);
|
|
if (returned_sense_len < ccb->csio.sense_len)
|
|
ccb->csio.sense_resid = ccb->csio.sense_len -
|
|
returned_sense_len;
|
|
else
|
|
ccb->csio.sense_resid = 0;
|
|
|
|
scsi_set_sense_data(&ccb->csio.sense_data, SSD_TYPE_FIXED,
|
|
1, skey, asc, ascq, SSD_ELEM_NONE);
|
|
ccb->ccb_h.status |= CAM_AUTOSNS_VALID;
|
|
|
|
return status;
|
|
}
|
|
|
|
/** mprsas_complete_nvme_unmap
|
|
*
|
|
* Complete native NVMe command issued using NVMe Encapsulated
|
|
* Request Message.
|
|
*/
|
|
static u8
|
|
mprsas_complete_nvme_unmap(struct mpr_softc *sc, struct mpr_command *cm)
|
|
{
|
|
Mpi26NVMeEncapsulatedErrorReply_t *mpi_reply;
|
|
struct nvme_completion *nvme_completion = NULL;
|
|
u8 scsi_status = MPI2_SCSI_STATUS_GOOD;
|
|
|
|
mpi_reply =(Mpi26NVMeEncapsulatedErrorReply_t *)cm->cm_reply;
|
|
if (le16toh(mpi_reply->ErrorResponseCount)){
|
|
nvme_completion = (struct nvme_completion *)cm->cm_sense;
|
|
scsi_status = mprsas_nvme_trans_status_code(
|
|
nvme_completion->status, cm);
|
|
}
|
|
return scsi_status;
|
|
}
|
|
|
|
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, *scsi_cdb;
|
|
int dir = 0, i;
|
|
u16 alloc_len;
|
|
struct mprsas_target *target;
|
|
target_id_t target_id;
|
|
|
|
MPR_FUNCTRACE(sc);
|
|
|
|
callout_stop(&cm->cm_callout);
|
|
mtx_assert(&sc->mpr_mtx, MA_OWNED);
|
|
|
|
sassc = sc->sassc;
|
|
ccb = cm->cm_complete_data;
|
|
csio = &ccb->csio;
|
|
target_id = csio->ccb_h.target_id;
|
|
rep = (MPI2_SCSI_IO_REPLY *)cm->cm_reply;
|
|
mpr_dprint(sc, MPR_TRACE,
|
|
"cm %p SMID %u ccb %p reply %p outstanding %u csio->scsi_status 0x%x,"
|
|
"csio->dxfer_len 0x%x, csio->msg_le 0x%xn\n", cm,
|
|
cm->cm_desc.Default.SMID, cm->cm_ccb, cm->cm_reply,
|
|
cm->cm_targ->outstanding, csio->scsi_status,
|
|
csio->dxfer_len, csio->msg_len);
|
|
/*
|
|
* 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_flags & MPR_CM_FLAGS_ON_RECOVERY) {
|
|
TAILQ_REMOVE(&cm->cm_targ->timedout_commands, cm, cm_recovery);
|
|
KASSERT(cm->cm_state == MPR_CM_STATE_BUSY,
|
|
("Not busy for CM_FLAGS_TIMEDOUT: %d\n", cm->cm_state));
|
|
cm->cm_flags &= ~MPR_CM_FLAGS_ON_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.
|
|
*/
|
|
mprsas_set_ccbstatus(ccb, 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_XINFO, "Error sending command, "
|
|
"freezing SIM queue\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Point to the SCSI CDB, which is dependent on the CAM_CDB_POINTER
|
|
* flag, and use it in a few places in the rest of this function for
|
|
* convenience. Use the macro if available.
|
|
*/
|
|
scsi_cdb = scsiio_cdb_ptr(csio);
|
|
|
|
/*
|
|
* 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 && (scsi_cdb[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 (mprsas_get_ccbstatus(ccb) == CAM_REQ_INPROG) {
|
|
if ((sc->mpr_flags & MPR_FLAGS_DIAGRESET) != 0)
|
|
mprsas_set_ccbstatus(ccb, CAM_SCSI_BUS_RESET);
|
|
else {
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
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 (mprsas_get_ccbstatus(ccb) != CAM_REQ_CMP) {
|
|
/*
|
|
* Freeze the dev queue so that commands are
|
|
* executed in the correct order 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;
|
|
}
|
|
|
|
target = &sassc->targets[target_id];
|
|
if (scsi_cdb[0] == UNMAP &&
|
|
target->is_nvme &&
|
|
(csio->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR) {
|
|
rep->SCSIStatus = mprsas_complete_nvme_unmap(sc, cm);
|
|
csio->scsi_status = rep->SCSIStatus;
|
|
}
|
|
|
|
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)) {
|
|
mprsas_set_ccbstatus(ccb, 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) {
|
|
mprsas_set_ccbstatus(ccb, 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[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)) {
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_ABORTED);
|
|
break;
|
|
}
|
|
|
|
/* Handle normal status and sense */
|
|
csio->scsi_status = rep->SCSIStatus;
|
|
if (rep->SCSIStatus == MPI2_SCSI_STATUS_GOOD)
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
else
|
|
mprsas_set_ccbstatus(ccb, 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 ((scsi_cdb[0] == INQUIRY) &&
|
|
(scsi_cdb[1] & SI_EVPD) &&
|
|
(scsi_cdb[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[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[target_id].TLR_bits;
|
|
*TLR_bits = (u8)MPI2_SCSIIO_CONTROL_NO_TLR;
|
|
TLR_on = (u8)MPI2_SCSIIO_CONTROL_TLR_ON;
|
|
alloc_len = ((u16)scsi_cdb[3] << 8) + scsi_cdb[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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 ((scsi_cdb[0] == INQUIRY) &&
|
|
(csio->data_ptr != NULL) &&
|
|
((csio->data_ptr[0] & 0x1f) == T_DIRECT) &&
|
|
(sc->mapping_table[target_id].device_info &
|
|
MPI2_SAS_DEVICE_INFO_SATA_DEVICE) &&
|
|
((sc->mapping_table[target_id].device_info &
|
|
MPI2_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) ==
|
|
MPI2_SAS_DEVICE_INFO_END_DEVICE)) {
|
|
target = &sassc->targets[target_id];
|
|
target->supports_SSU = TRUE;
|
|
mpr_dprint(sc, MPR_XINFO, "Target %d supports SSU\n",
|
|
target_id);
|
|
}
|
|
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)
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
else
|
|
mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE);
|
|
break;
|
|
case MPI2_IOCSTATUS_INVALID_SGL:
|
|
mpr_print_scsiio_cmd(sc, cm);
|
|
mprsas_set_ccbstatus(ccb, 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_flags & MPR_CM_FLAGS_TIMEDOUT)
|
|
mprsas_set_ccbstatus(ccb, CAM_CMD_TIMEOUT);
|
|
else
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_ABORTED);
|
|
break;
|
|
case MPI2_IOCSTATUS_SCSI_DATA_OVERRUN:
|
|
/* resid is ignored for this condition */
|
|
csio->resid = 0;
|
|
mprsas_set_ccbstatus(ccb, CAM_DATA_RUN_ERR);
|
|
break;
|
|
case MPI2_IOCSTATUS_SCSI_IOC_TERMINATED:
|
|
case MPI2_IOCSTATUS_SCSI_EXT_TERMINATED:
|
|
/*
|
|
* These can sometimes be transient transport-related
|
|
* errors, and sometimes persistent drive-related errors.
|
|
* We used to retry these without decrementing the retry
|
|
* count by returning CAM_REQUEUE_REQ. Unfortunately, if
|
|
* we hit a persistent drive problem that returns one of
|
|
* these error codes, we would retry indefinitely. So,
|
|
* return CAM_REQ_CMP_ERROR so that we decrement the retry
|
|
* count and avoid infinite retries. We're taking the
|
|
* potential risk of flagging false failures in the event
|
|
* of a topology-related error (e.g. a SAS expander problem
|
|
* causes a command addressed to a drive to fail), but
|
|
* avoiding getting into an infinite retry loop. However,
|
|
* if we get them while were moving a device, we should
|
|
* fail the request as 'not there' because the device
|
|
* is effectively gone.
|
|
*/
|
|
if (cm->cm_targ->flags & MPRSAS_TARGET_INREMOVAL)
|
|
mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE);
|
|
else
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP_ERR);
|
|
mpr_dprint(sc, MPR_INFO,
|
|
"Controller reported %s tgt %u SMID %u loginfo %x%s\n",
|
|
mpr_describe_table(mpr_iocstatus_string,
|
|
le16toh(rep->IOCStatus) & MPI2_IOCSTATUS_MASK),
|
|
target_id, cm->cm_desc.Default.SMID,
|
|
le32toh(rep->IOCLogInfo),
|
|
(cm->cm_targ->flags & MPRSAS_TARGET_INREMOVAL) ? " departing" : "");
|
|
mpr_dprint(sc, MPR_XINFO,
|
|
"SCSIStatus %x SCSIState %x xfercount %u\n",
|
|
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 loginfo %x scsi %x state %x xfer %u\n",
|
|
le16toh(rep->IOCStatus), le32toh(rep->IOCLogInfo),
|
|
rep->SCSIStatus, rep->SCSIState,
|
|
le32toh(rep->TransferCount));
|
|
csio->resid = cm->cm_length;
|
|
|
|
if (scsi_cdb[0] == UNMAP &&
|
|
target->is_nvme &&
|
|
(csio->ccb_h.flags & CAM_DATA_MASK) == CAM_DATA_VADDR)
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
else
|
|
mprsas_set_ccbstatus(ccb, 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 (mprsas_get_ccbstatus(ccb) != CAM_REQ_CMP) {
|
|
ccb->ccb_h.status |= CAM_DEV_QFRZN;
|
|
xpt_freeze_devq(ccb->ccb_h.path, /*count*/ 1);
|
|
}
|
|
|
|
/*
|
|
* Check to see if we're removing the device. If so, and this is the
|
|
* last command on the queue, proceed with the deferred removal of the
|
|
* device. Note, for removing a volume, this won't trigger because
|
|
* pending_remove_tm will be NULL.
|
|
*/
|
|
if (cm->cm_targ->flags & MPRSAS_TARGET_INREMOVAL) {
|
|
if (TAILQ_FIRST(&cm->cm_targ->commands) == NULL &&
|
|
cm->cm_targ->pending_remove_tm != NULL) {
|
|
mpr_dprint(sc, MPR_INFO, "Last pending command complete: starting remove_device\n");
|
|
mpr_map_command(sc, cm->cm_targ->pending_remove_tm);
|
|
cm->cm_targ->pending_remove_tm = NULL;
|
|
}
|
|
}
|
|
|
|
mpr_free_command(sc, cm);
|
|
xpt_done(ccb);
|
|
}
|
|
|
|
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);
|
|
mprsas_set_ccbstatus(ccb, 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__);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, 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)
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
else
|
|
mprsas_set_ccbstatus(ccb, 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;
|
|
|
|
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__);
|
|
mprsas_set_ccbstatus(ccb, 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__);
|
|
mprsas_set_ccbstatus(ccb, 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:
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_INVALID);
|
|
xpt_done(ccb);
|
|
return;
|
|
}
|
|
|
|
cm = mpr_alloc_command(sc);
|
|
if (cm == NULL) {
|
|
mpr_dprint(sc, MPR_ERROR, "%s: cannot allocate command\n",
|
|
__func__);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, 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);
|
|
mprsas_set_ccbstatus(ccb, CAM_DEV_NOT_THERE);
|
|
goto bailout;
|
|
}
|
|
mprsas_send_smpcmd(sassc, ccb, sasaddr);
|
|
|
|
return;
|
|
|
|
bailout:
|
|
xpt_done(ccb);
|
|
|
|
}
|
|
|
|
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 = mprsas_alloc_tm(sc);
|
|
if (tm == NULL) {
|
|
mpr_dprint(sc, MPR_ERROR, "command alloc failure in "
|
|
"mprsas_action_resetdev\n");
|
|
mprsas_set_ccbstatus(ccb, 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->TaskType = MPI2_SCSITASKMGMT_TASKTYPE_TARGET_RESET;
|
|
|
|
if (!targ->is_nvme || sc->custom_nvme_tm_handling) {
|
|
/* SAS Hard Link Reset / SATA Link Reset */
|
|
req->MsgFlags = MPI2_SCSITASKMGMT_MSGFLAGS_LINK_RESET;
|
|
} else {
|
|
/* PCIe Protocol Level Reset*/
|
|
req->MsgFlags =
|
|
MPI26_SCSITASKMGMT_MSGFLAGS_PROTOCOL_LVL_RST_PCIE;
|
|
}
|
|
|
|
tm->cm_data = NULL;
|
|
tm->cm_complete = mprsas_resetdev_complete;
|
|
tm->cm_complete_data = ccb;
|
|
|
|
mpr_dprint(sc, MPR_INFO, "%s: Sending reset for target ID %d\n",
|
|
__func__, targ->tid);
|
|
tm->cm_targ = targ;
|
|
|
|
mprsas_prepare_for_tm(sc, tm, targ, CAM_LUN_WILDCARD);
|
|
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);
|
|
mprsas_set_ccbstatus(ccb, 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) {
|
|
mprsas_set_ccbstatus(ccb, CAM_REQ_CMP);
|
|
mprsas_announce_reset(sc, AC_SENT_BDR, tm->cm_targ->tid,
|
|
CAM_LUN_WILDCARD);
|
|
}
|
|
else
|
|
mprsas_set_ccbstatus(ccb, 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_dprint(sassc->sc, MPR_XINFO, "%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) {
|
|
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;
|
|
|
|
/*
|
|
* 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 = CDAI_FLAG_NONE;
|
|
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 ((mprsas_get_ccbstatus((union ccb *)&cdai) == CAM_REQ_CMP)
|
|
&& (rcap_buf.prot & SRC16_PROT_EN)) {
|
|
switch (rcap_buf.prot & SRC16_P_TYPE) {
|
|
case SRC16_PTYPE_1:
|
|
case SRC16_PTYPE_3:
|
|
lun->eedp_formatted = TRUE;
|
|
lun->eedp_block_size =
|
|
scsi_4btoul(rcap_buf.length);
|
|
break;
|
|
case SRC16_PTYPE_2:
|
|
default:
|
|
lun->eedp_formatted = FALSE;
|
|
lun->eedp_block_size = 0;
|
|
break;
|
|
}
|
|
} else {
|
|
lun->eedp_formatted = FALSE;
|
|
lun->eedp_block_size = 0;
|
|
}
|
|
break;
|
|
}
|
|
case AC_FOUND_DEVICE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the INRESET flag for this target so that no I/O will be sent to
|
|
* the target until the reset has completed. If an I/O request does
|
|
* happen, the devq will be frozen. The CCB holds the path which is
|
|
* used to release the devq. The devq is released and the CCB is freed
|
|
* when the TM completes.
|
|
*/
|
|
void
|
|
mprsas_prepare_for_tm(struct mpr_softc *sc, struct mpr_command *tm,
|
|
struct mprsas_target *target, lun_id_t lun_id)
|
|
{
|
|
union ccb *ccb;
|
|
path_id_t path_id;
|
|
|
|
ccb = xpt_alloc_ccb_nowait();
|
|
if (ccb) {
|
|
path_id = cam_sim_path(sc->sassc->sim);
|
|
if (xpt_create_path(&ccb->ccb_h.path, xpt_periph, path_id,
|
|
target->tid, lun_id) != CAM_REQ_CMP) {
|
|
xpt_free_ccb(ccb);
|
|
} else {
|
|
tm->cm_ccb = ccb;
|
|
tm->cm_targ = target;
|
|
target->flags |= MPRSAS_TARGET_INRESET;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
void
|
|
mprsas_realloc_targets(struct mpr_softc *sc, int maxtargets)
|
|
{
|
|
struct mprsas_softc *sassc;
|
|
struct mprsas_lun *lun, *lun_tmp;
|
|
struct mprsas_target *targ;
|
|
int i;
|
|
|
|
sassc = sc->sassc;
|
|
/*
|
|
* The number of targets is based on IOC Facts, so free all of
|
|
* the allocated LUNs for each target and then the target buffer
|
|
* itself.
|
|
*/
|
|
for (i=0; i< 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);
|
|
|
|
sassc->targets = malloc(sizeof(struct mprsas_target) * maxtargets,
|
|
M_MPR, M_WAITOK|M_ZERO);
|
|
}
|