summaryrefslogtreecommitdiffstats
path: root/sys/dev/mpt/mpt_cam.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/mpt/mpt_cam.c')
-rw-r--r--sys/dev/mpt/mpt_cam.c1931
1 files changed, 1931 insertions, 0 deletions
diff --git a/sys/dev/mpt/mpt_cam.c b/sys/dev/mpt/mpt_cam.c
new file mode 100644
index 0000000..6277109
--- /dev/null
+++ b/sys/dev/mpt/mpt_cam.c
@@ -0,0 +1,1931 @@
+/*-
+ * FreeBSD/CAM specific routines for LSI '909 FC adapters.
+ * FreeBSD Version.
+ *
+ * Copyright (c) 2000, 2001 by Greg Ansley
+ *
+ * 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 immediately at the beginning of the file, without modification,
+ * this list of conditions, and the following disclaimer.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * Additional Copyright (c) 2002 by Matthew Jacob under same license.
+ */
+/*-
+ * Copyright (c) 2004, Avid Technology, Inc. and its contributors.
+ * Copyright (c) 2005, WHEEL Sp. z o.o.
+ * Copyright (c) 2004, 2005 Justin T. Gibbs
+ * 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 at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon including
+ * a substantially similar Disclaimer requirement for further binary
+ * redistribution.
+ * 3. Neither the name of the LSI Logic Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 THE COPYRIGHT
+ * OWNER OR CONTRIBUTOR IS ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <dev/mpt/mpt.h>
+#include <dev/mpt/mpt_cam.h>
+#include <dev/mpt/mpt_raid.h>
+
+#include "dev/mpt/mpilib/mpi_ioc.h" /* XXX Fix Event Handling!!! */
+#include "dev/mpt/mpilib/mpi_init.h"
+#include "dev/mpt/mpilib/mpi_targ.h"
+
+#include <sys/callout.h>
+#include <sys/kthread.h>
+
+static void mpt_poll(struct cam_sim *);
+static timeout_t mpt_timeout;
+static void mpt_action(struct cam_sim *, union ccb *);
+static int mpt_setwidth(struct mpt_softc *, int, int);
+static int mpt_setsync(struct mpt_softc *, int, int, int);
+static void mpt_calc_geometry(struct ccb_calc_geometry *ccg, int extended);
+static mpt_reply_handler_t mpt_scsi_reply_handler;
+static mpt_reply_handler_t mpt_scsi_tmf_reply_handler;
+static int mpt_scsi_reply_frame_handler(struct mpt_softc *mpt, request_t *req,
+ MSG_DEFAULT_REPLY *reply_frame);
+static int mpt_bus_reset(struct mpt_softc *, int /*sleep_ok*/);
+
+static int mpt_spawn_recovery_thread(struct mpt_softc *mpt);
+static void mpt_terminate_recovery_thread(struct mpt_softc *mpt);
+static void mpt_recovery_thread(void *arg);
+static int mpt_scsi_send_tmf(struct mpt_softc *, u_int /*type*/,
+ u_int /*flags*/, u_int /*channel*/,
+ u_int /*target*/, u_int /*lun*/,
+ u_int /*abort_ctx*/, int /*sleep_ok*/);
+static void mpt_recover_commands(struct mpt_softc *mpt);
+
+static uint32_t scsi_io_handler_id = MPT_HANDLER_ID_NONE;
+static uint32_t scsi_tmf_handler_id = MPT_HANDLER_ID_NONE;
+
+static mpt_probe_handler_t mpt_cam_probe;
+static mpt_attach_handler_t mpt_cam_attach;
+static mpt_event_handler_t mpt_cam_event;
+static mpt_reset_handler_t mpt_cam_ioc_reset;
+static mpt_detach_handler_t mpt_cam_detach;
+
+static struct mpt_personality mpt_cam_personality =
+{
+ .name = "mpt_cam",
+ .probe = mpt_cam_probe,
+ .attach = mpt_cam_attach,
+ .event = mpt_cam_event,
+ .reset = mpt_cam_ioc_reset,
+ .detach = mpt_cam_detach,
+};
+
+DECLARE_MPT_PERSONALITY(mpt_cam, SI_ORDER_SECOND);
+
+int
+mpt_cam_probe(struct mpt_softc *mpt)
+{
+ /*
+ * Only attach to nodes that support the initiator
+ * role or have RAID physical devices that need
+ * CAM pass-thru support.
+ */
+ if ((mpt->mpt_proto_flags & MPI_PORTFACTS_PROTOCOL_INITIATOR) != 0
+ || (mpt->ioc_page2 != NULL && mpt->ioc_page2->MaxPhysDisks != 0))
+ return (0);
+ return (ENODEV);
+}
+
+int
+mpt_cam_attach(struct mpt_softc *mpt)
+{
+ struct cam_devq *devq;
+ mpt_handler_t handler;
+ int maxq;
+ int error;
+
+ MPTLOCK_2_CAMLOCK(mpt);
+ TAILQ_INIT(&mpt->request_timeout_list);
+ mpt->bus = 0;
+ maxq = (mpt->mpt_global_credits < MPT_MAX_REQUESTS(mpt))?
+ mpt->mpt_global_credits : MPT_MAX_REQUESTS(mpt);
+
+ handler.reply_handler = mpt_scsi_reply_handler;
+ error = mpt_register_handler(mpt, MPT_HANDLER_REPLY, handler,
+ &scsi_io_handler_id);
+ if (error != 0)
+ goto cleanup;
+ handler.reply_handler = mpt_scsi_tmf_reply_handler;
+ error = mpt_register_handler(mpt, MPT_HANDLER_REPLY, handler,
+ &scsi_tmf_handler_id);
+ if (error != 0)
+ goto cleanup;
+
+ /*
+ * We keep one request reserved for timeout TMF requests.
+ */
+ mpt->tmf_req = mpt_get_request(mpt, /*sleep_ok*/FALSE);
+ if (mpt->tmf_req == NULL) {
+ mpt_prt(mpt, "Unable to allocate dedicated TMF request!\n");
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Mark the request as free even though not on the free list.
+ * There is only one TMF request allowed to be outstanding at
+ * a time and the TMF routines perform their own allocation
+ * tracking using the standard state flags.
+ */
+ mpt->tmf_req->state = REQ_STATE_FREE;
+ maxq--;
+
+ if (mpt_spawn_recovery_thread(mpt) != 0) {
+ mpt_prt(mpt, "Unable to spawn recovery thread!\n");
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Create the device queue for our SIM(s).
+ */
+ devq = cam_simq_alloc(maxq);
+ if (devq == NULL) {
+ mpt_prt(mpt, "Unable to allocate CAM SIMQ!\n");
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Construct our SIM entry.
+ */
+ mpt->sim = cam_sim_alloc(mpt_action, mpt_poll, "mpt", mpt,
+ mpt->unit, 1, maxq, devq);
+ if (mpt->sim == NULL) {
+ mpt_prt(mpt, "Unable to allocate CAM SIM!\n");
+ cam_simq_free(devq);
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Register exactly the bus.
+ */
+ if (xpt_bus_register(mpt->sim, 0) != CAM_SUCCESS) {
+ mpt_prt(mpt, "Bus registration Failed!\n");
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ if (xpt_create_path(&mpt->path, NULL, cam_sim_path(mpt->sim),
+ CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
+ mpt_prt(mpt, "Unable to allocate Path!\n");
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Only register a second bus for RAID physical
+ * devices if the controller supports RAID.
+ */
+ if (mpt->ioc_page2 == NULL
+ || mpt->ioc_page2->MaxPhysDisks == 0)
+ return (0);
+
+ /*
+ * Create a "bus" to export all hidden disks to CAM.
+ */
+ mpt->phydisk_sim = cam_sim_alloc(mpt_action, mpt_poll, "mpt", mpt,
+ mpt->unit, 1, maxq, devq);
+ if (mpt->phydisk_sim == NULL) {
+ mpt_prt(mpt, "Unable to allocate Physical Disk CAM SIM!\n");
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Register exactly the bus.
+ */
+ if (xpt_bus_register(mpt->phydisk_sim, 1) != CAM_SUCCESS) {
+ mpt_prt(mpt, "Physical Disk Bus registration Failed!\n");
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ if (xpt_create_path(&mpt->phydisk_path, NULL,
+ cam_sim_path(mpt->phydisk_sim),
+ CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
+ mpt_prt(mpt, "Unable to allocate Physical Disk Path!\n");
+ error = ENOMEM;
+ goto cleanup;
+ }
+
+ CAMLOCK_2_MPTLOCK(mpt);
+ return (0);
+cleanup:
+ CAMLOCK_2_MPTLOCK(mpt);
+ mpt_cam_detach(mpt);
+ return (error);
+}
+
+void
+mpt_cam_detach(struct mpt_softc *mpt)
+{
+ mpt_handler_t handler;
+
+ mpt_terminate_recovery_thread(mpt);
+
+ handler.reply_handler = mpt_scsi_reply_handler;
+ mpt_deregister_handler(mpt, MPT_HANDLER_REPLY, handler,
+ scsi_io_handler_id);
+ handler.reply_handler = mpt_scsi_tmf_reply_handler;
+ mpt_deregister_handler(mpt, MPT_HANDLER_REPLY, handler,
+ scsi_tmf_handler_id);
+
+ if (mpt->tmf_req != NULL) {
+ mpt_free_request(mpt, mpt->tmf_req);
+ mpt->tmf_req = NULL;
+ }
+
+ if (mpt->sim != NULL) {
+ xpt_free_path(mpt->path);
+ xpt_bus_deregister(cam_sim_path(mpt->sim));
+ cam_sim_free(mpt->sim, TRUE);
+ mpt->sim = NULL;
+ }
+
+ if (mpt->phydisk_sim != NULL) {
+ xpt_free_path(mpt->phydisk_path);
+ xpt_bus_deregister(cam_sim_path(mpt->phydisk_sim));
+ cam_sim_free(mpt->phydisk_sim, TRUE);
+ mpt->phydisk_sim = NULL;
+ }
+}
+
+/* This routine is used after a system crash to dump core onto the
+ * swap device.
+ */
+static void
+mpt_poll(struct cam_sim *sim)
+{
+ struct mpt_softc *mpt;
+
+ mpt = (struct mpt_softc *)cam_sim_softc(sim);
+ MPT_LOCK(mpt);
+ mpt_intr(mpt);
+ MPT_UNLOCK(mpt);
+}
+
+/*
+ * Watchdog timeout routine for SCSI requests.
+ */
+static void
+mpt_timeout(void *arg)
+{
+ union ccb *ccb;
+ struct mpt_softc *mpt;
+ request_t *req;
+
+ ccb = (union ccb *)arg;
+#if NOTYET
+ mpt = mpt_find_softc(mpt);
+ if (mpt == NULL)
+ return;
+#else
+ mpt = ccb->ccb_h.ccb_mpt_ptr;
+#endif
+
+ MPT_LOCK(mpt);
+ req = ccb->ccb_h.ccb_req_ptr;
+ mpt_prt(mpt, "Request %p Timed out.\n", req);
+ if ((req->state & REQ_STATE_QUEUED) == REQ_STATE_QUEUED) {
+ TAILQ_REMOVE(&mpt->request_pending_list, req, links);
+ TAILQ_INSERT_TAIL(&mpt->request_timeout_list, req, links);
+ req->state |= REQ_STATE_TIMEDOUT;
+ mpt_wakeup_recovery_thread(mpt);
+ }
+ MPT_UNLOCK(mpt);
+}
+
+/*
+ * Callback routine from "bus_dmamap_load" or, in simple cases, called directly.
+ *
+ * Takes a list of physical segments and builds the SGL for SCSI IO command
+ * and forwards the commard to the IOC after one last check that CAM has not
+ * aborted the transaction.
+ */
+static void
+mpt_execute_req(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error)
+{
+ request_t *req;
+ union ccb *ccb;
+ struct mpt_softc *mpt;
+ MSG_SCSI_IO_REQUEST *mpt_req;
+ SGE_SIMPLE32 *se;
+
+ req = (request_t *)arg;
+ ccb = req->ccb;
+
+ mpt = ccb->ccb_h.ccb_mpt_ptr;
+ req = ccb->ccb_h.ccb_req_ptr;
+ mpt_req = req->req_vbuf;
+
+ if (error == 0 && nseg > MPT_SGL_MAX) {
+ error = EFBIG;
+ }
+
+ if (error != 0) {
+ if (error != EFBIG)
+ mpt_prt(mpt, "bus_dmamap_load returned %d\n", error);
+ if (ccb->ccb_h.status == CAM_REQ_INPROG) {
+ xpt_freeze_devq(ccb->ccb_h.path, 1);
+ ccb->ccb_h.status = CAM_DEV_QFRZN;
+ if (error == EFBIG)
+ ccb->ccb_h.status |= CAM_REQ_TOO_BIG;
+ else
+ ccb->ccb_h.status |= CAM_REQ_CMP_ERR;
+ }
+ ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
+ xpt_done(ccb);
+ CAMLOCK_2_MPTLOCK(mpt);
+ mpt_free_request(mpt, req);
+ MPTLOCK_2_CAMLOCK(mpt);
+ return;
+ }
+
+ if (nseg > MPT_NSGL_FIRST(mpt)) {
+ int i, nleft = nseg;
+ uint32_t flags;
+ bus_dmasync_op_t op;
+ SGE_CHAIN32 *ce;
+
+ mpt_req->DataLength = ccb->csio.dxfer_len;
+ flags = MPI_SGE_FLAGS_SIMPLE_ELEMENT;
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT)
+ flags |= MPI_SGE_FLAGS_HOST_TO_IOC;
+
+ se = (SGE_SIMPLE32 *) &mpt_req->SGL;
+ for (i = 0; i < MPT_NSGL_FIRST(mpt) - 1; i++, se++, dm_segs++) {
+ uint32_t tf;
+
+ bzero(se, sizeof (*se));
+ se->Address = dm_segs->ds_addr;
+ MPI_pSGE_SET_LENGTH(se, dm_segs->ds_len);
+ tf = flags;
+ if (i == MPT_NSGL_FIRST(mpt) - 2) {
+ tf |= MPI_SGE_FLAGS_LAST_ELEMENT;
+ }
+ MPI_pSGE_SET_FLAGS(se, tf);
+ nleft -= 1;
+ }
+
+ /*
+ * Tell the IOC where to find the first chain element
+ */
+ mpt_req->ChainOffset = ((char *)se - (char *)mpt_req) >> 2;
+
+ /*
+ * Until we're finished with all segments...
+ */
+ while (nleft) {
+ int ntodo;
+ /*
+ * Construct the chain element that point to the
+ * next segment.
+ */
+ ce = (SGE_CHAIN32 *) se++;
+ if (nleft > MPT_NSGL(mpt)) {
+ ntodo = MPT_NSGL(mpt) - 1;
+ ce->NextChainOffset = (MPT_RQSL(mpt) -
+ sizeof (SGE_SIMPLE32)) >> 2;
+ ce->Length = MPT_NSGL(mpt) *
+ sizeof (SGE_SIMPLE32);
+ } else {
+ ntodo = nleft;
+ ce->NextChainOffset = 0;
+ ce->Length = ntodo * sizeof (SGE_SIMPLE32);
+ }
+ ce->Address = req->req_pbuf +
+ ((char *)se - (char *)mpt_req);
+ ce->Flags = MPI_SGE_FLAGS_CHAIN_ELEMENT;
+ for (i = 0; i < ntodo; i++, se++, dm_segs++) {
+ uint32_t tf;
+
+ bzero(se, sizeof (*se));
+ se->Address = dm_segs->ds_addr;
+ MPI_pSGE_SET_LENGTH(se, dm_segs->ds_len);
+ tf = flags;
+ if (i == ntodo - 1) {
+ tf |= MPI_SGE_FLAGS_LAST_ELEMENT;
+ if (ce->NextChainOffset == 0) {
+ tf |=
+ MPI_SGE_FLAGS_END_OF_LIST |
+ MPI_SGE_FLAGS_END_OF_BUFFER;
+ }
+ }
+ MPI_pSGE_SET_FLAGS(se, tf);
+ nleft -= 1;
+ }
+
+ }
+
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
+ op = BUS_DMASYNC_PREREAD;
+ else
+ op = BUS_DMASYNC_PREWRITE;
+ if (!(ccb->ccb_h.flags & (CAM_SG_LIST_PHYS|CAM_DATA_PHYS))) {
+ bus_dmamap_sync(mpt->buffer_dmat, req->dmap, op);
+ }
+ } else if (nseg > 0) {
+ int i;
+ uint32_t flags;
+ bus_dmasync_op_t op;
+
+ mpt_req->DataLength = ccb->csio.dxfer_len;
+ flags = MPI_SGE_FLAGS_SIMPLE_ELEMENT;
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT)
+ flags |= MPI_SGE_FLAGS_HOST_TO_IOC;
+
+ /* Copy the segments into our SG list */
+ se = (SGE_SIMPLE32 *) &mpt_req->SGL;
+ for (i = 0; i < nseg; i++, se++, dm_segs++) {
+ uint32_t tf;
+
+ bzero(se, sizeof (*se));
+ se->Address = dm_segs->ds_addr;
+ MPI_pSGE_SET_LENGTH(se, dm_segs->ds_len);
+ tf = flags;
+ if (i == nseg - 1) {
+ tf |=
+ MPI_SGE_FLAGS_LAST_ELEMENT |
+ MPI_SGE_FLAGS_END_OF_BUFFER |
+ MPI_SGE_FLAGS_END_OF_LIST;
+ }
+ MPI_pSGE_SET_FLAGS(se, tf);
+ }
+
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
+ op = BUS_DMASYNC_PREREAD;
+ else
+ op = BUS_DMASYNC_PREWRITE;
+ if (!(ccb->ccb_h.flags & (CAM_SG_LIST_PHYS|CAM_DATA_PHYS))) {
+ bus_dmamap_sync(mpt->buffer_dmat, req->dmap, op);
+ }
+ } else {
+ se = (SGE_SIMPLE32 *) &mpt_req->SGL;
+ /*
+ * No data to transfer so we just make a single simple SGL
+ * with zero length.
+ */
+ MPI_pSGE_SET_FLAGS(se,
+ (MPI_SGE_FLAGS_LAST_ELEMENT | MPI_SGE_FLAGS_END_OF_BUFFER |
+ MPI_SGE_FLAGS_SIMPLE_ELEMENT | MPI_SGE_FLAGS_END_OF_LIST));
+ }
+
+ /*
+ * Last time we need to check if this CCB needs to be aborted.
+ */
+ if (ccb->ccb_h.status != CAM_REQ_INPROG) {
+ if (nseg && (ccb->ccb_h.flags & CAM_SG_LIST_PHYS) == 0)
+ bus_dmamap_unload(mpt->buffer_dmat, req->dmap);
+ CAMLOCK_2_MPTLOCK(mpt);
+ mpt_free_request(mpt, req);
+ MPTLOCK_2_CAMLOCK(mpt);
+ xpt_done(ccb);
+ return;
+ }
+
+ ccb->ccb_h.status |= CAM_SIM_QUEUED;
+ CAMLOCK_2_MPTLOCK(mpt);
+ if (ccb->ccb_h.timeout != CAM_TIME_INFINITY) {
+ ccb->ccb_h.timeout_ch =
+ timeout(mpt_timeout, (caddr_t)ccb,
+ (ccb->ccb_h.timeout * hz) / 1000);
+ } else {
+ callout_handle_init(&ccb->ccb_h.timeout_ch);
+ }
+ if (mpt->verbose >= MPT_PRT_DEBUG)
+ mpt_print_scsi_io_request(mpt_req);
+ mpt_send_cmd(mpt, req);
+ MPTLOCK_2_CAMLOCK(mpt);
+}
+
+static void
+mpt_start(struct cam_sim *sim, union ccb *ccb)
+{
+ request_t *req;
+ struct mpt_softc *mpt;
+ MSG_SCSI_IO_REQUEST *mpt_req;
+ struct ccb_scsiio *csio = &ccb->csio;
+ struct ccb_hdr *ccbh = &ccb->ccb_h;
+ int raid_passthru;
+
+ /* Get the pointer for the physical addapter */
+ mpt = ccb->ccb_h.ccb_mpt_ptr;
+ raid_passthru = (sim == mpt->phydisk_sim);
+
+ CAMLOCK_2_MPTLOCK(mpt);
+ /* Get a request structure off the free list */
+ if ((req = mpt_get_request(mpt, /*sleep_ok*/FALSE)) == NULL) {
+ if (mpt->outofbeer == 0) {
+ mpt->outofbeer = 1;
+ xpt_freeze_simq(mpt->sim, 1);
+ mpt_lprt(mpt, MPT_PRT_DEBUG, "FREEZEQ\n");
+ }
+ MPTLOCK_2_CAMLOCK(mpt);
+ ccb->ccb_h.status = CAM_REQUEUE_REQ;
+ xpt_done(ccb);
+ return;
+ }
+
+ MPTLOCK_2_CAMLOCK(mpt);
+
+#if 0
+ COWWWWW
+ if (raid_passthru) {
+ status = mpt_raid_quiesce_disk(mpt, mpt->raid_disks + ccb->ccb_h.target_id,
+ request_t *req)
+#endif
+
+ /*
+ * Link the ccb and the request structure so we can find
+ * the other knowing either the request or the ccb
+ */
+ req->ccb = ccb;
+ ccb->ccb_h.ccb_req_ptr = req;
+
+ /* Now we build the command for the IOC */
+ mpt_req = req->req_vbuf;
+ bzero(mpt_req, sizeof *mpt_req);
+
+ mpt_req->Function = MPI_FUNCTION_SCSI_IO_REQUEST;
+ if (raid_passthru)
+ mpt_req->Function = MPI_FUNCTION_RAID_SCSI_IO_PASSTHROUGH;
+
+ mpt_req->Bus = mpt->bus;
+
+ mpt_req->SenseBufferLength =
+ (csio->sense_len < MPT_SENSE_SIZE) ?
+ csio->sense_len : MPT_SENSE_SIZE;
+
+ /*
+ * We use the message context to find the request structure when we
+ * Get the command completion interrupt from the IOC.
+ */
+ mpt_req->MsgContext = htole32(req->index | scsi_io_handler_id);
+
+ /* Which physical device to do the I/O on */
+ mpt_req->TargetID = ccb->ccb_h.target_id;
+ /*
+ * XXX Assumes Single level, Single byte, CAM LUN type.
+ */
+ mpt_req->LUN[1] = ccb->ccb_h.target_lun;
+
+ /* Set the direction of the transfer */
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
+ mpt_req->Control = MPI_SCSIIO_CONTROL_READ;
+ else if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT)
+ mpt_req->Control = MPI_SCSIIO_CONTROL_WRITE;
+ else
+ mpt_req->Control = MPI_SCSIIO_CONTROL_NODATATRANSFER;
+
+ if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) {
+ switch(ccb->csio.tag_action) {
+ case MSG_HEAD_OF_Q_TAG:
+ mpt_req->Control |= MPI_SCSIIO_CONTROL_HEADOFQ;
+ break;
+ case MSG_ACA_TASK:
+ mpt_req->Control |= MPI_SCSIIO_CONTROL_ACAQ;
+ break;
+ case MSG_ORDERED_Q_TAG:
+ mpt_req->Control |= MPI_SCSIIO_CONTROL_ORDEREDQ;
+ break;
+ case MSG_SIMPLE_Q_TAG:
+ default:
+ mpt_req->Control |= MPI_SCSIIO_CONTROL_SIMPLEQ;
+ break;
+ }
+ } else {
+ if (mpt->is_fc)
+ mpt_req->Control |= MPI_SCSIIO_CONTROL_SIMPLEQ;
+ else
+ /* XXX No such thing for a target doing packetized. */
+ mpt_req->Control |= MPI_SCSIIO_CONTROL_UNTAGGED;
+ }
+
+ if (mpt->is_fc == 0) {
+ if (ccb->ccb_h.flags & CAM_DIS_DISCONNECT) {
+ mpt_req->Control |= MPI_SCSIIO_CONTROL_NO_DISCONNECT;
+ }
+ }
+
+ /* Copy the scsi command block into place */
+ if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0)
+ bcopy(csio->cdb_io.cdb_ptr, mpt_req->CDB, csio->cdb_len);
+ else
+ bcopy(csio->cdb_io.cdb_bytes, mpt_req->CDB, csio->cdb_len);
+
+ mpt_req->CDBLength = csio->cdb_len;
+ mpt_req->DataLength = csio->dxfer_len;
+ mpt_req->SenseBufferLowAddr = req->sense_pbuf;
+
+ /*
+ * If we have any data to send with this command,
+ * map it into bus space.
+ */
+
+ if ((ccbh->flags & CAM_DIR_MASK) != CAM_DIR_NONE) {
+ if ((ccbh->flags & CAM_SCATTER_VALID) == 0) {
+ /*
+ * We've been given a pointer to a single buffer.
+ */
+ if ((ccbh->flags & CAM_DATA_PHYS) == 0) {
+ /*
+ * Virtual address that needs to translated into
+ * one or more physical address ranges.
+ */
+ int error;
+
+ error = bus_dmamap_load(mpt->buffer_dmat,
+ req->dmap, csio->data_ptr, csio->dxfer_len,
+ mpt_execute_req, req, 0);
+ if (error == EINPROGRESS) {
+ /*
+ * So as to maintain ordering,
+ * freeze the controller queue
+ * until our mapping is
+ * returned.
+ */
+ xpt_freeze_simq(mpt->sim, 1);
+ ccbh->status |= CAM_RELEASE_SIMQ;
+ }
+ } else {
+ /*
+ * We have been given a pointer to single
+ * physical buffer.
+ */
+ struct bus_dma_segment seg;
+ seg.ds_addr =
+ (bus_addr_t)(vm_offset_t)csio->data_ptr;
+ seg.ds_len = csio->dxfer_len;
+ mpt_execute_req(req, &seg, 1, 0);
+ }
+ } else {
+ /*
+ * We have been given a list of addresses.
+ * This case could be easily supported but they are not
+ * currently generated by the CAM subsystem so there
+ * is no point in wasting the time right now.
+ */
+ struct bus_dma_segment *segs;
+ if ((ccbh->flags & CAM_SG_LIST_PHYS) == 0) {
+ mpt_execute_req(req, NULL, 0, EFAULT);
+ } else {
+ /* Just use the segments provided */
+ segs = (struct bus_dma_segment *)csio->data_ptr;
+ mpt_execute_req(req, segs, csio->sglist_cnt,
+ (csio->sglist_cnt < MPT_SGL_MAX)?
+ 0 : EFBIG);
+ }
+ }
+ } else {
+ mpt_execute_req(req, NULL, 0, 0);
+ }
+}
+
+static int
+mpt_bus_reset(struct mpt_softc *mpt, int sleep_ok)
+{
+ int error;
+ u_int status;
+
+ error = mpt_scsi_send_tmf(mpt, MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS,
+ mpt->is_fc ? MPI_SCSITASKMGMT_MSGFLAGS_LIP_RESET_OPTION : 0,
+ /*bus*/0, /*target_id*/0, /*target_lun*/0, /*abort_ctx*/0,
+ sleep_ok);
+
+ if (error != 0) {
+ /*
+ * mpt_scsi_send_tmf hard resets on failure, so no
+ * need to do so here.
+ */
+ mpt_prt(mpt,
+ "mpt_bus_reset: mpt_scsi_send_tmf returned %d\n", error);
+ return (EIO);
+ }
+
+ /* Wait for bus reset to be processed by the IOC. */
+ error = mpt_wait_req(mpt, mpt->tmf_req, REQ_STATE_DONE,
+ REQ_STATE_DONE, sleep_ok, /*time_ms*/5000);
+
+ status = mpt->tmf_req->IOCStatus;
+ mpt->tmf_req->state = REQ_STATE_FREE;
+ if (error) {
+ mpt_prt(mpt, "mpt_bus_reset: Reset timed-out."
+ "Resetting controller.\n");
+ mpt_reset(mpt, /*reinit*/TRUE);
+ return (ETIMEDOUT);
+ } else if ((status & MPI_IOCSTATUS_MASK) != MPI_SCSI_STATUS_SUCCESS) {
+ mpt_prt(mpt, "mpt_bus_reset: TMF Status %d."
+ "Resetting controller.\n", status);
+ mpt_reset(mpt, /*reinit*/TRUE);
+ return (EIO);
+ }
+ return (0);
+}
+
+static int
+mpt_cam_event(struct mpt_softc *mpt, request_t *req,
+ MSG_EVENT_NOTIFY_REPLY *msg)
+{
+ switch(msg->Event & 0xFF) {
+ case MPI_EVENT_UNIT_ATTENTION:
+ mpt_prt(mpt, "Bus: 0x%02x TargetID: 0x%02x\n",
+ (msg->Data[0] >> 8) & 0xff, msg->Data[0] & 0xff);
+ break;
+
+ case MPI_EVENT_IOC_BUS_RESET:
+ /* We generated a bus reset */
+ mpt_prt(mpt, "IOC Bus Reset Port: %d\n",
+ (msg->Data[0] >> 8) & 0xff);
+ xpt_async(AC_BUS_RESET, mpt->path, NULL);
+ break;
+
+ case MPI_EVENT_EXT_BUS_RESET:
+ /* Someone else generated a bus reset */
+ mpt_prt(mpt, "Ext Bus Reset\n");
+ /*
+ * These replies don't return EventData like the MPI
+ * spec says they do
+ */
+ xpt_async(AC_BUS_RESET, mpt->path, NULL);
+ break;
+
+ case MPI_EVENT_RESCAN:
+ /*
+ * In general this means a device has been added
+ * to the loop.
+ */
+ mpt_prt(mpt, "Rescan Port: %d\n", (msg->Data[0] >> 8) & 0xff);
+/* xpt_async(AC_FOUND_DEVICE, path, NULL); */
+ break;
+
+ case MPI_EVENT_LINK_STATUS_CHANGE:
+ mpt_prt(mpt, "Port %d: LinkState: %s\n",
+ (msg->Data[1] >> 8) & 0xff,
+ ((msg->Data[0] & 0xff) == 0)? "Failed" : "Active");
+ break;
+
+ case MPI_EVENT_LOOP_STATE_CHANGE:
+ switch ((msg->Data[0] >> 16) & 0xff) {
+ case 0x01:
+ mpt_prt(mpt,
+ "Port 0x%x: FC LinkEvent: LIP(%02x,%02x) "
+ "(Loop Initialization)\n",
+ (msg->Data[1] >> 8) & 0xff,
+ (msg->Data[0] >> 8) & 0xff,
+ (msg->Data[0] ) & 0xff);
+ switch ((msg->Data[0] >> 8) & 0xff) {
+ case 0xF7:
+ if ((msg->Data[0] & 0xff) == 0xF7) {
+ printf("Device needs AL_PA\n");
+ } else {
+ printf("Device %02x doesn't like "
+ "FC performance\n",
+ msg->Data[0] & 0xFF);
+ }
+ break;
+ case 0xF8:
+ if ((msg->Data[0] & 0xff) == 0xF7) {
+ printf("Device had loop failure at its "
+ "receiver prior to acquiring "
+ "AL_PA\n");
+ } else {
+ printf("Device %02x detected loop "
+ "failure at its receiver\n",
+ msg->Data[0] & 0xFF);
+ }
+ break;
+ default:
+ printf("Device %02x requests that device "
+ "%02x reset itself\n",
+ msg->Data[0] & 0xFF,
+ (msg->Data[0] >> 8) & 0xFF);
+ break;
+ }
+ break;
+ case 0x02:
+ mpt_prt(mpt, "Port 0x%x: FC LinkEvent: "
+ "LPE(%02x,%02x) (Loop Port Enable)\n",
+ (msg->Data[1] >> 8) & 0xff, /* Port */
+ (msg->Data[0] >> 8) & 0xff, /* Character 3 */
+ (msg->Data[0] ) & 0xff /* Character 4 */);
+ break;
+ case 0x03:
+ mpt_prt(mpt, "Port 0x%x: FC LinkEvent: "
+ "LPB(%02x,%02x) (Loop Port Bypass)\n",
+ (msg->Data[1] >> 8) & 0xff, /* Port */
+ (msg->Data[0] >> 8) & 0xff, /* Character 3 */
+ (msg->Data[0] ) & 0xff /* Character 4 */);
+ break;
+ default:
+ mpt_prt(mpt, "Port 0x%x: FC LinkEvent: Unknown "
+ "FC event (%02x %02x %02x)\n",
+ (msg->Data[1] >> 8) & 0xff, /* Port */
+ (msg->Data[0] >> 16) & 0xff, /* Event */
+ (msg->Data[0] >> 8) & 0xff, /* Character 3 */
+ (msg->Data[0] ) & 0xff /* Character 4 */);
+ }
+ break;
+
+ case MPI_EVENT_LOGOUT:
+ mpt_prt(mpt, "FC Logout Port: %d N_PortID: %02x\n",
+ (msg->Data[1] >> 8) & 0xff, msg->Data[0]);
+ break;
+ default:
+ return (/*handled*/0);
+ }
+ return (/*handled*/1);
+}
+
+/*
+ * Reply path for all SCSI I/O requests, called from our
+ * interrupt handler by extracting our handler index from
+ * the MsgContext field of the reply from the IOC.
+ *
+ * This routine is optimized for the common case of a
+ * completion without error. All exception handling is
+ * offloaded to non-inlined helper routines to minimize
+ * cache footprint.
+ */
+static int
+mpt_scsi_reply_handler(struct mpt_softc *mpt, request_t *req,
+ MSG_DEFAULT_REPLY *reply_frame)
+{
+ MSG_SCSI_IO_REQUEST *scsi_req;
+ union ccb *ccb;
+
+ scsi_req = (MSG_SCSI_IO_REQUEST *)req->req_vbuf;
+ ccb = req->ccb;
+ if (ccb == NULL) {
+ mpt_prt(mpt, "Completion without CCB. Flags %#x, Func %#x\n",
+ req->state, scsi_req->Function);
+ mpt_print_scsi_io_request(scsi_req);
+ return (/*free_reply*/TRUE);
+ }
+
+ untimeout(mpt_timeout, ccb, ccb->ccb_h.timeout_ch);
+
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) {
+ bus_dmasync_op_t op;
+
+ if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
+ op = BUS_DMASYNC_POSTREAD;
+ else
+ op = BUS_DMASYNC_POSTWRITE;
+ bus_dmamap_sync(mpt->buffer_dmat, req->dmap, op);
+ bus_dmamap_unload(mpt->buffer_dmat, req->dmap);
+ }
+
+ if (reply_frame == NULL) {
+ /*
+ * Context only reply, completion
+ * without error status.
+ */
+ ccb->csio.resid = 0;
+ mpt_set_ccb_status(ccb, CAM_REQ_CMP);
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ } else {
+ mpt_scsi_reply_frame_handler(mpt, req, reply_frame);
+ }
+
+ if (mpt->outofbeer) {
+ ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
+ mpt->outofbeer = 0;
+ mpt_lprt(mpt, MPT_PRT_DEBUG, "THAWQ\n");
+ }
+ ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
+ MPTLOCK_2_CAMLOCK(mpt);
+ if (scsi_req->Function == MPI_FUNCTION_RAID_SCSI_IO_PASSTHROUGH
+ && scsi_req->CDB[0] == INQUIRY
+ && (scsi_req->CDB[1] & SI_EVPD) == 0) {
+ struct scsi_inquiry_data *inq;
+
+ /*
+ * Fake out the device type so that only the
+ * pass-thru device will attach.
+ */
+ inq = (struct scsi_inquiry_data *)ccb->csio.data_ptr;
+ inq->device &= ~0x1F;
+ inq->device |= T_NODEVICE;
+ }
+ xpt_done(ccb);
+ CAMLOCK_2_MPTLOCK(mpt);
+ if ((req->state & REQ_STATE_TIMEDOUT) == 0)
+ TAILQ_REMOVE(&mpt->request_pending_list, req, links);
+ else
+ TAILQ_REMOVE(&mpt->request_timeout_list, req, links);
+
+ if ((req->state & REQ_STATE_NEED_WAKEUP) == 0) {
+ mpt_free_request(mpt, req);
+ return (/*free_reply*/TRUE);
+ }
+ req->state &= ~REQ_STATE_QUEUED;
+ req->state |= REQ_STATE_DONE;
+ wakeup(req);
+ return (/*free_reply*/TRUE);
+}
+
+static int
+mpt_scsi_tmf_reply_handler(struct mpt_softc *mpt, request_t *req,
+ MSG_DEFAULT_REPLY *reply_frame)
+{
+ MSG_SCSI_TASK_MGMT_REPLY *tmf_reply;
+ u_int status;
+
+ mpt_lprt(mpt, MPT_PRT_DEBUG, "TMF Complete: req %p, reply %p\n",
+ req, reply_frame);
+ KASSERT(req == mpt->tmf_req, ("TMF Reply not using mpt->tmf_req"));
+
+ tmf_reply = (MSG_SCSI_TASK_MGMT_REPLY *)reply_frame;
+
+ /* Record status of TMF for any waiters. */
+ req->IOCStatus = tmf_reply->IOCStatus;
+ status = le16toh(tmf_reply->IOCStatus);
+ mpt_lprt(mpt, MPT_PRT_DEBUG, "TMF Complete: status 0x%x\n", status);
+ TAILQ_REMOVE(&mpt->request_pending_list, req, links);
+ if ((req->state & REQ_STATE_NEED_WAKEUP) != 0) {
+ req->state |= REQ_STATE_DONE;
+ wakeup(req);
+ } else
+ mpt->tmf_req->state = REQ_STATE_FREE;
+
+ return (/*free_reply*/TRUE);
+}
+
+/*
+ * Clean up all SCSI Initiator personality state in response
+ * to a controller reset.
+ */
+static void
+mpt_cam_ioc_reset(struct mpt_softc *mpt, int type)
+{
+ /*
+ * The pending list is already run down by
+ * the generic handler. Perform the same
+ * operation on the timed out request list.
+ */
+ mpt_complete_request_chain(mpt, &mpt->request_timeout_list,
+ MPI_IOCSTATUS_INVALID_STATE);
+
+ /*
+ * Inform the XPT that a bus reset has occurred.
+ */
+ xpt_async(AC_BUS_RESET, mpt->path, NULL);
+}
+
+/*
+ * Parse additional completion information in the reply
+ * frame for SCSI I/O requests.
+ */
+static int
+mpt_scsi_reply_frame_handler(struct mpt_softc *mpt, request_t *req,
+ MSG_DEFAULT_REPLY *reply_frame)
+{
+ union ccb *ccb;
+ MSG_SCSI_IO_REPLY *scsi_io_reply;
+ u_int ioc_status;
+ u_int sstate;
+ u_int loginfo;
+
+ MPT_DUMP_REPLY_FRAME(mpt, reply_frame);
+ KASSERT(reply_frame->Function == MPI_FUNCTION_SCSI_IO_REQUEST
+ || reply_frame->Function == MPI_FUNCTION_RAID_SCSI_IO_PASSTHROUGH,
+ ("MPT SCSI I/O Handler called with incorrect reply type"));
+ KASSERT((reply_frame->MsgFlags & MPI_MSGFLAGS_CONTINUATION_REPLY) == 0,
+ ("MPT SCSI I/O Handler called with continuation reply"));
+
+ scsi_io_reply = (MSG_SCSI_IO_REPLY *)reply_frame;
+ ioc_status = le16toh(scsi_io_reply->IOCStatus);
+ loginfo = ioc_status & MPI_IOCSTATUS_FLAG_LOG_INFO_AVAILABLE;
+ ioc_status &= MPI_IOCSTATUS_MASK;
+ sstate = scsi_io_reply->SCSIState;
+
+ ccb = req->ccb;
+ ccb->csio.resid =
+ ccb->csio.dxfer_len - le32toh(scsi_io_reply->TransferCount);
+
+ if ((sstate & MPI_SCSI_STATE_AUTOSENSE_VALID) != 0
+ && (ccb->ccb_h.flags & (CAM_SENSE_PHYS | CAM_SENSE_PTR)) == 0) {
+ ccb->ccb_h.status |= CAM_AUTOSNS_VALID;
+ ccb->csio.sense_resid =
+ ccb->csio.sense_len - scsi_io_reply->SenseCount;
+ bcopy(req->sense_vbuf, &ccb->csio.sense_data,
+ min(ccb->csio.sense_len, scsi_io_reply->SenseCount));
+ }
+
+ if ((sstate & MPI_SCSI_STATE_QUEUE_TAG_REJECTED) != 0) {
+ /*
+ * Tag messages rejected, but non-tagged retry
+ * was successful.
+XXXX
+ mpt_set_tags(mpt, devinfo, MPT_QUEUE_NONE);
+ */
+ }
+
+ switch(ioc_status) {
+ case MPI_IOCSTATUS_SCSI_RESIDUAL_MISMATCH:
+ /*
+ * XXX
+ * Linux driver indicates that a zero
+ * transfer length with this error code
+ * indicates a CRC error.
+ *
+ * No need to swap the bytes for checking
+ * against zero.
+ */
+ if (scsi_io_reply->TransferCount == 0) {
+ mpt_set_ccb_status(ccb, CAM_UNCOR_PARITY);
+ break;
+ }
+ /* FALLTHROUGH */
+ case MPI_IOCSTATUS_SCSI_DATA_UNDERRUN:
+ case MPI_IOCSTATUS_SUCCESS:
+ case MPI_IOCSTATUS_SCSI_RECOVERED_ERROR:
+ if ((sstate & MPI_SCSI_STATE_NO_SCSI_STATUS) != 0) {
+ /*
+ * Status was never returned for this transaction.
+ */
+ mpt_set_ccb_status(ccb, CAM_UNEXP_BUSFREE);
+ } else if (scsi_io_reply->SCSIStatus != SCSI_STATUS_OK) {
+ ccb->csio.scsi_status = scsi_io_reply->SCSIStatus;
+ mpt_set_ccb_status(ccb, CAM_SCSI_STATUS_ERROR);
+ if ((sstate & MPI_SCSI_STATE_AUTOSENSE_FAILED) != 0)
+ mpt_set_ccb_status(ccb, CAM_AUTOSENSE_FAIL);
+ } else if ((sstate & MPI_SCSI_STATE_RESPONSE_INFO_VALID) != 0) {
+
+ /* XXX Handle SPI-Packet and FCP-2 reponse info. */
+ mpt_set_ccb_status(ccb, CAM_REQ_CMP_ERR);
+ } else
+ mpt_set_ccb_status(ccb, CAM_REQ_CMP);
+ break;
+ case MPI_IOCSTATUS_SCSI_DATA_OVERRUN:
+ mpt_set_ccb_status(ccb, CAM_DATA_RUN_ERR);
+ break;
+ case MPI_IOCSTATUS_SCSI_IO_DATA_ERROR:
+ mpt_set_ccb_status(ccb, CAM_UNCOR_PARITY);
+ break;
+ case MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE:
+ /*
+ * Since selection timeouts and "device really not
+ * there" are grouped into this error code, report
+ * selection timeout. Selection timeouts are
+ * typically retried before giving up on the device
+ * whereas "device not there" errors are considered
+ * unretryable.
+ */
+ mpt_set_ccb_status(ccb, CAM_SEL_TIMEOUT);
+ break;
+ case MPI_IOCSTATUS_SCSI_PROTOCOL_ERROR:
+ mpt_set_ccb_status(ccb, CAM_SEQUENCE_FAIL);
+ break;
+ case MPI_IOCSTATUS_SCSI_INVALID_BUS:
+ mpt_set_ccb_status(ccb, CAM_PATH_INVALID);
+ break;
+ case MPI_IOCSTATUS_SCSI_INVALID_TARGETID:
+ mpt_set_ccb_status(ccb, CAM_TID_INVALID);
+ break;
+ case MPI_IOCSTATUS_SCSI_TASK_MGMT_FAILED:
+ ccb->ccb_h.status = CAM_UA_TERMIO;
+ break;
+ case MPI_IOCSTATUS_INVALID_STATE:
+ /*
+ * The IOC has been reset. Emulate a bus reset.
+ */
+ /* FALLTHROUGH */
+ case MPI_IOCSTATUS_SCSI_EXT_TERMINATED:
+ ccb->ccb_h.status = CAM_SCSI_BUS_RESET;
+ break;
+ case MPI_IOCSTATUS_SCSI_TASK_TERMINATED:
+ case MPI_IOCSTATUS_SCSI_IOC_TERMINATED:
+ /*
+ * Don't clobber any timeout status that has
+ * already been set for this transaction. We
+ * want the SCSI layer to be able to differentiate
+ * between the command we aborted due to timeout
+ * and any innocent bystanders.
+ */
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_INPROG)
+ break;
+ mpt_set_ccb_status(ccb, CAM_REQ_TERMIO);
+ break;
+
+ case MPI_IOCSTATUS_INSUFFICIENT_RESOURCES:
+ mpt_set_ccb_status(ccb, CAM_RESRC_UNAVAIL);
+ break;
+ case MPI_IOCSTATUS_BUSY:
+ mpt_set_ccb_status(ccb, CAM_BUSY);
+ break;
+ case MPI_IOCSTATUS_INVALID_FUNCTION:
+ case MPI_IOCSTATUS_INVALID_SGL:
+ case MPI_IOCSTATUS_INTERNAL_ERROR:
+ case MPI_IOCSTATUS_INVALID_FIELD:
+ default:
+ /* XXX
+ * Some of the above may need to kick
+ * of a recovery action!!!!
+ */
+ ccb->ccb_h.status = CAM_UNREC_HBA_ERROR;
+ break;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)
+ mpt_freeze_ccb(ccb);
+
+ return (/*free_reply*/TRUE);
+}
+
+static void
+mpt_action(struct cam_sim *sim, union ccb *ccb)
+{
+ struct mpt_softc *mpt;
+ struct ccb_trans_settings *cts;
+ u_int tgt;
+ int raid_passthru;
+
+ CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("mpt_action\n"));
+
+ mpt = (struct mpt_softc *)cam_sim_softc(sim);
+ raid_passthru = (sim == mpt->phydisk_sim);
+
+ tgt = ccb->ccb_h.target_id;
+ if (raid_passthru
+ && ccb->ccb_h.func_code != XPT_PATH_INQ
+ && ccb->ccb_h.func_code != XPT_RESET_BUS) {
+ CAMLOCK_2_MPTLOCK(mpt);
+ if (mpt_map_physdisk(mpt, ccb, &tgt) != 0) {
+ ccb->ccb_h.status = CAM_DEV_NOT_THERE;
+ MPTLOCK_2_CAMLOCK(mpt);
+ xpt_done(ccb);
+ return;
+ }
+ MPTLOCK_2_CAMLOCK(mpt);
+ }
+
+ ccb->ccb_h.ccb_mpt_ptr = mpt;
+
+ switch (ccb->ccb_h.func_code) {
+ case XPT_SCSI_IO: /* Execute the requested I/O operation */
+ /*
+ * Do a couple of preliminary checks...
+ */
+ if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) {
+ if ((ccb->ccb_h.flags & CAM_CDB_PHYS) != 0) {
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ break;
+ }
+ }
+ /* Max supported CDB length is 16 bytes */
+ /* XXX Unless we implement the new 32byte message type */
+ if (ccb->csio.cdb_len >
+ sizeof (((PTR_MSG_SCSI_IO_REQUEST)0)->CDB)) {
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ return;
+ }
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ mpt_start(sim, ccb);
+ break;
+
+ case XPT_RESET_BUS:
+ mpt_lprt(mpt, MPT_PRT_DEBUG, "XPT_RESET_BUS\n");
+ if (!raid_passthru) {
+ CAMLOCK_2_MPTLOCK(mpt);
+ (void)mpt_bus_reset(mpt, /*sleep_ok*/FALSE);
+ MPTLOCK_2_CAMLOCK(mpt);
+ }
+ /*
+ * mpt_bus_reset is always successful in that it
+ * will fall back to a hard reset should a bus
+ * reset attempt fail.
+ */
+ mpt_set_ccb_status(ccb, CAM_REQ_CMP);
+ xpt_done(ccb);
+ break;
+
+ case XPT_ABORT:
+ /*
+ * XXX: Need to implement
+ */
+ ccb->ccb_h.status = CAM_UA_ABORT;
+ xpt_done(ccb);
+ break;
+
+#ifdef CAM_NEW_TRAN_CODE
+#define IS_CURRENT_SETTINGS(c) (c->type == CTS_TYPE_CURRENT_SETTINGS)
+#else
+#define IS_CURRENT_SETTINGS(c) (c->flags & CCB_TRANS_CURRENT_SETTINGS)
+#endif
+#define DP_DISC_ENABLE 0x1
+#define DP_DISC_DISABL 0x2
+#define DP_DISC (DP_DISC_ENABLE|DP_DISC_DISABL)
+
+#define DP_TQING_ENABLE 0x4
+#define DP_TQING_DISABL 0x8
+#define DP_TQING (DP_TQING_ENABLE|DP_TQING_DISABL)
+
+#define DP_WIDE 0x10
+#define DP_NARROW 0x20
+#define DP_WIDTH (DP_WIDE|DP_NARROW)
+
+#define DP_SYNC 0x40
+
+ case XPT_SET_TRAN_SETTINGS: /* Nexus Settings */
+ cts = &ccb->cts;
+ if (!IS_CURRENT_SETTINGS(cts)) {
+ mpt_prt(mpt, "Attempt to set User settings\n");
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ break;
+ }
+ if (mpt->is_fc == 0) {
+ uint8_t dval = 0;
+ u_int period = 0, offset = 0;
+#ifndef CAM_NEW_TRAN_CODE
+ if (cts->valid & CCB_TRANS_DISC_VALID) {
+ dval |= DP_DISC_ENABLE;
+ }
+ if (cts->valid & CCB_TRANS_TQ_VALID) {
+ dval |= DP_TQING_ENABLE;
+ }
+ if (cts->valid & CCB_TRANS_BUS_WIDTH_VALID) {
+ if (cts->bus_width)
+ dval |= DP_WIDE;
+ else
+ dval |= DP_NARROW;
+ }
+ /*
+ * Any SYNC RATE of nonzero and SYNC_OFFSET
+ * of nonzero will cause us to go to the
+ * selected (from NVRAM) maximum value for
+ * this device. At a later point, we'll
+ * allow finer control.
+ */
+ if ((cts->valid & CCB_TRANS_SYNC_RATE_VALID) &&
+ (cts->valid & CCB_TRANS_SYNC_OFFSET_VALID)) {
+ dval |= DP_SYNC;
+ period = cts->sync_period;
+ offset = cts->sync_offset;
+ }
+#else
+ struct ccb_trans_settings_scsi *scsi =
+ &cts->proto_specific.scsi;
+ struct ccb_trans_settings_spi *spi =
+ &cts->xport_specific.spi;
+
+ if ((spi->valid & CTS_SPI_VALID_DISC) != 0) {
+ if ((spi->flags & CTS_SPI_FLAGS_DISC_ENB) != 0)
+ dval |= DP_DISC_ENABLE;
+ else
+ dval |= DP_DISC_DISABL;
+ }
+
+ if ((scsi->valid & CTS_SCSI_VALID_TQ) != 0) {
+ if ((scsi->flags & CTS_SCSI_FLAGS_TAG_ENB) != 0)
+ dval |= DP_TQING_ENABLE;
+ else
+ dval |= DP_TQING_DISABL;
+ }
+
+ if ((spi->valid & CTS_SPI_VALID_BUS_WIDTH) != 0) {
+ if (spi->bus_width == MSG_EXT_WDTR_BUS_16_BIT)
+ dval |= DP_WIDE;
+ else
+ dval |= DP_NARROW;
+ }
+
+ if ((spi->valid & CTS_SPI_VALID_SYNC_OFFSET) &&
+ (spi->valid & CTS_SPI_VALID_SYNC_RATE) &&
+ (spi->sync_period && spi->sync_offset)) {
+ dval |= DP_SYNC;
+ period = spi->sync_period;
+ offset = spi->sync_offset;
+ }
+#endif
+ CAMLOCK_2_MPTLOCK(mpt);
+ if (dval & DP_DISC_ENABLE) {
+ mpt->mpt_disc_enable |= (1 << tgt);
+ } else if (dval & DP_DISC_DISABL) {
+ mpt->mpt_disc_enable &= ~(1 << tgt);
+ }
+ if (dval & DP_TQING_ENABLE) {
+ mpt->mpt_tag_enable |= (1 << tgt);
+ } else if (dval & DP_TQING_DISABL) {
+ mpt->mpt_tag_enable &= ~(1 << tgt);
+ }
+ if (dval & DP_WIDTH) {
+ if (mpt_setwidth(mpt, tgt, dval & DP_WIDE)) {
+mpt_prt(mpt, "Set width Failed!\n");
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ MPTLOCK_2_CAMLOCK(mpt);
+ xpt_done(ccb);
+ break;
+ }
+ }
+ if (dval & DP_SYNC) {
+ if (mpt_setsync(mpt, tgt, period, offset)) {
+mpt_prt(mpt, "Set sync Failed!\n");
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR;
+ MPTLOCK_2_CAMLOCK(mpt);
+ xpt_done(ccb);
+ break;
+ }
+ }
+ MPTLOCK_2_CAMLOCK(mpt);
+ mpt_lprt(mpt, MPT_PRT_DEBUG,
+ "SET tgt %d flags %x period %x off %x\n",
+ tgt, dval, period, offset);
+ }
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ break;
+
+ case XPT_GET_TRAN_SETTINGS:
+ cts = &ccb->cts;
+ if (mpt->is_fc) {
+#ifndef CAM_NEW_TRAN_CODE
+ /*
+ * a lot of normal SCSI things don't make sense.
+ */
+ cts->flags = CCB_TRANS_TAG_ENB | CCB_TRANS_DISC_ENB;
+ cts->valid = CCB_TRANS_DISC_VALID | CCB_TRANS_TQ_VALID;
+ /*
+ * How do you measure the width of a high
+ * speed serial bus? Well, in bytes.
+ *
+ * Offset and period make no sense, though, so we set
+ * (above) a 'base' transfer speed to be gigabit.
+ */
+ cts->bus_width = MSG_EXT_WDTR_BUS_8_BIT;
+#else
+ struct ccb_trans_settings_fc *fc =
+ &cts->xport_specific.fc;
+
+ cts->protocol = PROTO_SCSI;
+ cts->protocol_version = SCSI_REV_2;
+ cts->transport = XPORT_FC;
+ cts->transport_version = 0;
+
+ fc->valid = CTS_FC_VALID_SPEED;
+ fc->bitrate = 100000; /* XXX: Need for 2Gb/s */
+ /* XXX: need a port database for each target */
+#endif
+ } else {
+#ifdef CAM_NEW_TRAN_CODE
+ struct ccb_trans_settings_scsi *scsi =
+ &cts->proto_specific.scsi;
+ struct ccb_trans_settings_spi *spi =
+ &cts->xport_specific.spi;
+#endif
+ uint8_t dval, pval, oval;
+ int rv;
+
+ /*
+ * We aren't going off of Port PAGE2 params for
+ * tagged queuing or disconnect capabilities
+ * for current settings. For goal settings,
+ * we assert all capabilities- we've had some
+ * problems with reading NVRAM data.
+ */
+ if (IS_CURRENT_SETTINGS(cts)) {
+ CONFIG_PAGE_SCSI_DEVICE_0 tmp;
+ dval = 0;
+
+ tmp = mpt->mpt_dev_page0[tgt];
+ CAMLOCK_2_MPTLOCK(mpt);
+ rv = mpt_read_cur_cfg_page(mpt, tgt,
+ &tmp.Header,
+ sizeof(tmp),
+ /*sleep_ok*/FALSE,
+ /*timeout_ms*/5000);
+ if (rv) {
+ mpt_prt(mpt,
+ "cannot get target %d DP0\n", tgt);
+ }
+ mpt_lprt(mpt, MPT_PRT_DEBUG,
+ "SPI Tgt %d Page 0: NParms %x "
+ "Information %x\n", tgt,
+ tmp.NegotiatedParameters,
+ tmp.Information);
+ MPTLOCK_2_CAMLOCK(mpt);
+
+ if (tmp.NegotiatedParameters &
+ MPI_SCSIDEVPAGE0_NP_WIDE)
+ dval |= DP_WIDE;
+
+ if (mpt->mpt_disc_enable & (1 << tgt)) {
+ dval |= DP_DISC_ENABLE;
+ }
+ if (mpt->mpt_tag_enable & (1 << tgt)) {
+ dval |= DP_TQING_ENABLE;
+ }
+ oval = (tmp.NegotiatedParameters >> 16) & 0xff;
+ pval = (tmp.NegotiatedParameters >> 8) & 0xff;
+ } else {
+ /*
+ * XXX: Fix wrt NVRAM someday. Attempts
+ * XXX: to read port page2 device data
+ * XXX: just returns zero in these areas.
+ */
+ dval = DP_WIDE|DP_DISC|DP_TQING;
+ oval = (mpt->mpt_port_page0.Capabilities >> 16);
+ pval = (mpt->mpt_port_page0.Capabilities >> 8);
+ }
+#ifndef CAM_NEW_TRAN_CODE
+ cts->flags &= ~(CCB_TRANS_DISC_ENB|CCB_TRANS_TAG_ENB);
+ if (dval & DP_DISC_ENABLE) {
+ cts->flags |= CCB_TRANS_DISC_ENB;
+ }
+ if (dval & DP_TQING_ENABLE) {
+ cts->flags |= CCB_TRANS_TAG_ENB;
+ }
+ if (dval & DP_WIDE) {
+ cts->bus_width = MSG_EXT_WDTR_BUS_16_BIT;
+ } else {
+ cts->bus_width = MSG_EXT_WDTR_BUS_8_BIT;
+ }
+ cts->valid = CCB_TRANS_BUS_WIDTH_VALID |
+ CCB_TRANS_DISC_VALID | CCB_TRANS_TQ_VALID;
+ if (oval) {
+ cts->sync_period = pval;
+ cts->sync_offset = oval;
+ cts->valid |=
+ CCB_TRANS_SYNC_RATE_VALID |
+ CCB_TRANS_SYNC_OFFSET_VALID;
+ }
+#else
+ cts->protocol = PROTO_SCSI;
+ cts->protocol_version = SCSI_REV_2;
+ cts->transport = XPORT_SPI;
+ cts->transport_version = 2;
+
+ scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB;
+ spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB;
+ if (dval & DP_DISC_ENABLE) {
+ spi->flags |= CTS_SPI_FLAGS_DISC_ENB;
+ }
+ if (dval & DP_TQING_ENABLE) {
+ scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB;
+ }
+ if (oval && pval) {
+ spi->sync_offset = oval;
+ spi->sync_period = pval;
+ spi->valid |= CTS_SPI_VALID_SYNC_OFFSET;
+ spi->valid |= CTS_SPI_VALID_SYNC_RATE;
+ }
+ spi->valid |= CTS_SPI_VALID_BUS_WIDTH;
+ if (dval & DP_WIDE) {
+ spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT;
+ } else {
+ spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT;
+ }
+ if (cts->ccb_h.target_lun != CAM_LUN_WILDCARD) {
+ scsi->valid = CTS_SCSI_VALID_TQ;
+ spi->valid |= CTS_SPI_VALID_DISC;
+ } else {
+ scsi->valid = 0;
+ }
+#endif
+ mpt_lprt(mpt, MPT_PRT_DEBUG,
+ "GET %s tgt %d flags %x period %x offset %x\n",
+ IS_CURRENT_SETTINGS(cts)
+ ? "ACTIVE" : "NVRAM",
+ tgt, dval, pval, oval);
+ }
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ break;
+
+ case XPT_CALC_GEOMETRY:
+ {
+ struct ccb_calc_geometry *ccg;
+
+ ccg = &ccb->ccg;
+ if (ccg->block_size == 0) {
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ break;
+ }
+
+ mpt_calc_geometry(ccg, /*extended*/1);
+ xpt_done(ccb);
+ break;
+ }
+ case XPT_PATH_INQ: /* Path routing inquiry */
+ {
+ struct ccb_pathinq *cpi = &ccb->cpi;
+
+ cpi->version_num = 1;
+ cpi->target_sprt = 0;
+ cpi->hba_eng_cnt = 0;
+ cpi->max_lun = 7;
+ cpi->bus_id = cam_sim_bus(sim);
+ /* XXX Report base speed more accurately for FC/SAS, etc.*/
+ if (raid_passthru) {
+ cpi->max_target = mpt->ioc_page2->MaxPhysDisks;
+ cpi->hba_misc = PIM_NOBUSRESET;
+ cpi->initiator_id = cpi->max_target + 1;
+ cpi->hba_inquiry = PI_TAG_ABLE;
+ if (mpt->is_fc) {
+ cpi->base_transfer_speed = 100000;
+ } else {
+ cpi->base_transfer_speed = 3300;
+ cpi->hba_inquiry |=
+ PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16;
+ }
+ } else if (mpt->is_fc) {
+ cpi->max_target = 255;
+ cpi->hba_misc = PIM_NOBUSRESET;
+ cpi->initiator_id = cpi->max_target + 1;
+ cpi->base_transfer_speed = 100000;
+ cpi->hba_inquiry = PI_TAG_ABLE;
+ } else {
+ cpi->initiator_id = mpt->mpt_ini_id;
+ cpi->base_transfer_speed = 3300;
+ cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16;
+ cpi->hba_misc = 0;
+ cpi->max_target = 15;
+ }
+
+ strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
+ strncpy(cpi->hba_vid, "LSI", HBA_IDLEN);
+ strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
+ cpi->unit_number = cam_sim_unit(sim);
+ cpi->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ break;
+ }
+ default:
+ ccb->ccb_h.status = CAM_REQ_INVALID;
+ xpt_done(ccb);
+ break;
+ }
+}
+
+static int
+mpt_setwidth(struct mpt_softc *mpt, int tgt, int onoff)
+{
+ CONFIG_PAGE_SCSI_DEVICE_1 tmp;
+ int rv;
+
+ tmp = mpt->mpt_dev_page1[tgt];
+ if (onoff) {
+ tmp.RequestedParameters |= MPI_SCSIDEVPAGE1_RP_WIDE;
+ } else {
+ tmp.RequestedParameters &= ~MPI_SCSIDEVPAGE1_RP_WIDE;
+ }
+ rv = mpt_write_cur_cfg_page(mpt, tgt, &tmp.Header, sizeof(tmp),
+ /*sleep_ok*/FALSE, /*timeout_ms*/5000);
+ if (rv) {
+ mpt_prt(mpt, "mpt_setwidth: write cur page failed\n");
+ return (-1);
+ }
+ rv = mpt_read_cur_cfg_page(mpt, tgt, &tmp.Header, sizeof(tmp),
+ /*sleep_ok*/FALSE, /*timeout_ms*/5000);
+ if (rv) {
+ mpt_prt(mpt, "mpt_setwidth: read cur page failed\n");
+ return (-1);
+ }
+ mpt->mpt_dev_page1[tgt] = tmp;
+ mpt_lprt(mpt, MPT_PRT_DEBUG,
+ "SPI Target %d Page 1: RequestedParameters %x Config %x\n",
+ tgt, mpt->mpt_dev_page1[tgt].RequestedParameters,
+ mpt->mpt_dev_page1[tgt].Configuration);
+ return (0);
+}
+
+static int
+mpt_setsync(struct mpt_softc *mpt, int tgt, int period, int offset)
+{
+ CONFIG_PAGE_SCSI_DEVICE_1 tmp;
+ int rv;
+
+ tmp = mpt->mpt_dev_page1[tgt];
+ tmp.RequestedParameters &= ~MPI_SCSIDEVPAGE1_RP_MIN_SYNC_PERIOD_MASK;
+ tmp.RequestedParameters &= ~MPI_SCSIDEVPAGE1_RP_MAX_SYNC_OFFSET_MASK;
+ tmp.RequestedParameters &= ~MPI_SCSIDEVPAGE1_RP_DT;
+ tmp.RequestedParameters &= ~MPI_SCSIDEVPAGE1_RP_QAS;
+ tmp.RequestedParameters &= ~MPI_SCSIDEVPAGE1_RP_IU;
+ /*
+ * XXX: For now, we're ignoring specific settings
+ */
+ if (period && offset) {
+ int factor, offset, np;
+ factor = (mpt->mpt_port_page0.Capabilities >> 8) & 0xff;
+ offset = (mpt->mpt_port_page0.Capabilities >> 16) & 0xff;
+ np = 0;
+ if (factor < 0x9) {
+ np |= MPI_SCSIDEVPAGE1_RP_QAS;
+ np |= MPI_SCSIDEVPAGE1_RP_IU;
+ }
+ if (factor < 0xa) {
+ np |= MPI_SCSIDEVPAGE1_RP_DT;
+ }
+ np |= (factor << 8) | (offset << 16);
+ tmp.RequestedParameters |= np;
+ }
+ rv = mpt_write_cur_cfg_page(mpt, tgt, &tmp.Header, sizeof(tmp),
+ /*sleep_ok*/FALSE, /*timeout_ms*/5000);
+ if (rv) {
+ mpt_prt(mpt, "mpt_setsync: write cur page failed\n");
+ return (-1);
+ }
+ rv = mpt_read_cur_cfg_page(mpt, tgt, &tmp.Header, sizeof(tmp),
+ /*sleep_ok*/FALSE, /*timeout_ms*/500);
+ if (rv) {
+ mpt_prt(mpt, "mpt_setsync: read cur page failed\n");
+ return (-1);
+ }
+ mpt->mpt_dev_page1[tgt] = tmp;
+ mpt_lprt(mpt, MPT_PRT_DEBUG,
+ "SPI Target %d Page 1: RParams %x Config %x\n",
+ tgt, mpt->mpt_dev_page1[tgt].RequestedParameters,
+ mpt->mpt_dev_page1[tgt].Configuration);
+ return (0);
+}
+
+static void
+mpt_calc_geometry(struct ccb_calc_geometry *ccg, int extended)
+{
+#if __FreeBSD_version >= 500000
+ cam_calc_geometry(ccg, extended);
+#else
+ uint32_t size_mb;
+ uint32_t secs_per_cylinder;
+
+ size_mb = ccg->volume_size / ((1024L * 1024L) / ccg->block_size);
+ if (size_mb > 1024 && extended) {
+ ccg->heads = 255;
+ ccg->secs_per_track = 63;
+ } else {
+ ccg->heads = 64;
+ ccg->secs_per_track = 32;
+ }
+ secs_per_cylinder = ccg->heads * ccg->secs_per_track;
+ ccg->cylinders = ccg->volume_size / secs_per_cylinder;
+ ccg->ccb_h.status = CAM_REQ_CMP;
+#endif
+}
+
+/****************************** Timeout Recovery ******************************/
+static int
+mpt_spawn_recovery_thread(struct mpt_softc *mpt)
+{
+ int error;
+
+ error = mpt_kthread_create(mpt_recovery_thread, mpt,
+ &mpt->recovery_thread, /*flags*/0,
+ /*altstack*/0, "mpt_recovery%d", mpt->unit);
+ return (error);
+}
+
+/*
+ * Lock is not held on entry.
+ */
+static void
+mpt_terminate_recovery_thread(struct mpt_softc *mpt)
+{
+
+ MPT_LOCK(mpt);
+ if (mpt->recovery_thread == NULL) {
+ MPT_UNLOCK(mpt);
+ return;
+ }
+ mpt->shutdwn_recovery = 1;
+ wakeup(mpt);
+ /*
+ * Sleep on a slightly different location
+ * for this interlock just for added safety.
+ */
+ mpt_sleep(mpt, &mpt->recovery_thread, PUSER, "thtrm", 0);
+ MPT_UNLOCK(mpt);
+}
+
+static void
+mpt_recovery_thread(void *arg)
+{
+ struct mpt_softc *mpt;
+
+#if __FreeBSD_version >= 500000
+ mtx_lock(&Giant);
+#endif
+ mpt = (struct mpt_softc *)arg;
+ MPT_LOCK(mpt);
+ for (;;) {
+
+ if (TAILQ_EMPTY(&mpt->request_timeout_list) != 0
+ && mpt->shutdwn_recovery == 0)
+ mpt_sleep(mpt, mpt, PUSER, "idle", 0);
+
+ if (mpt->shutdwn_recovery != 0)
+ break;
+
+ MPT_UNLOCK(mpt);
+ mpt_recover_commands(mpt);
+ MPT_LOCK(mpt);
+ }
+ mpt->recovery_thread = NULL;
+ wakeup(&mpt->recovery_thread);
+ MPT_UNLOCK(mpt);
+#if __FreeBSD_version >= 500000
+ mtx_unlock(&Giant);
+#endif
+ kthread_exit(0);
+}
+
+static int
+mpt_scsi_send_tmf(struct mpt_softc *mpt, u_int type,
+ u_int flags, u_int channel, u_int target, u_int lun,
+ u_int abort_ctx, int sleep_ok)
+{
+ MSG_SCSI_TASK_MGMT *tmf_req;
+ int error;
+
+ /*
+ * Wait for any current TMF request to complete.
+ * We're only allowed to issue one TMF at a time.
+ */
+ error = mpt_wait_req(mpt, mpt->tmf_req, REQ_STATE_FREE, REQ_STATE_MASK,
+ sleep_ok, MPT_TMF_MAX_TIMEOUT);
+ if (error != 0) {
+ mpt_reset(mpt, /*reinit*/TRUE);
+ return (ETIMEDOUT);
+ }
+
+ mpt->tmf_req->state = REQ_STATE_ALLOCATED|REQ_STATE_QUEUED;
+ TAILQ_INSERT_HEAD(&mpt->request_pending_list, mpt->tmf_req, links);
+
+ tmf_req = (MSG_SCSI_TASK_MGMT *)mpt->tmf_req->req_vbuf;
+ bzero(tmf_req, sizeof(*tmf_req));
+ tmf_req->TargetID = target;
+ tmf_req->Bus = channel;
+ tmf_req->ChainOffset = 0;
+ tmf_req->Function = MPI_FUNCTION_SCSI_TASK_MGMT;
+ tmf_req->Reserved = 0;
+ tmf_req->TaskType = type;
+ tmf_req->Reserved1 = 0;
+ tmf_req->MsgFlags = flags;
+ tmf_req->MsgContext =
+ htole32(mpt->tmf_req->index | scsi_tmf_handler_id);
+ bzero(&tmf_req->LUN, sizeof(tmf_req->LUN) + sizeof(tmf_req->Reserved2));
+ tmf_req->LUN[1] = lun;
+ tmf_req->TaskMsgContext = abort_ctx;
+
+ mpt_lprt(mpt, MPT_PRT_DEBUG,
+ "Issuing TMF %p with MsgContext of 0x%x\n", tmf_req,
+ tmf_req->MsgContext);
+ if (mpt->verbose > MPT_PRT_DEBUG)
+ mpt_print_request(tmf_req);
+
+ error = mpt_send_handshake_cmd(mpt, sizeof(*tmf_req), tmf_req);
+ if (error != 0)
+ mpt_reset(mpt, /*reinit*/TRUE);
+ return (error);
+}
+
+/*
+ * When a command times out, it is placed on the requeust_timeout_list
+ * and we wake our recovery thread. The MPT-Fusion architecture supports
+ * only a single TMF operation at a time, so we serially abort/bdr, etc,
+ * the timedout transactions. The next TMF is issued either by the
+ * completion handler of the current TMF waking our recovery thread,
+ * or the TMF timeout handler causing a hard reset sequence.
+ */
+static void
+mpt_recover_commands(struct mpt_softc *mpt)
+{
+ request_t *req;
+ union ccb *ccb;
+ int error;
+
+ MPT_LOCK(mpt);
+
+ /*
+ * Flush any commands whose completion coincides
+ * with their timeout.
+ */
+ mpt_intr(mpt);
+
+ if (TAILQ_EMPTY(&mpt->request_timeout_list) != 0) {
+ /*
+ * The timedout commands have already
+ * completed. This typically means
+ * that either the timeout value was on
+ * the hairy edge of what the device
+ * requires or - more likely - interrupts
+ * are not happening.
+ */
+ mpt_prt(mpt, "Timedout requests already complete. "
+ "Interrupts may not be functioning.\n");
+ MPT_UNLOCK(mpt);
+ return;
+ }
+
+ /*
+ * We have no visibility into the current state of the
+ * controller, so attempt to abort the commands in the
+ * order they timed-out.
+ */
+ while ((req = TAILQ_FIRST(&mpt->request_timeout_list)) != NULL) {
+ u_int status;
+
+ mpt_prt(mpt, "Attempting to Abort Req %p\n", req);
+
+ ccb = req->ccb;
+ mpt_set_ccb_status(ccb, CAM_CMD_TIMEOUT);
+ error = mpt_scsi_send_tmf(mpt,
+ MPI_SCSITASKMGMT_TASKTYPE_ABORT_TASK,
+ /*MsgFlags*/0, mpt->bus, ccb->ccb_h.target_id,
+ ccb->ccb_h.target_lun,
+ htole32(req->index | scsi_io_handler_id), /*sleep_ok*/TRUE);
+
+ if (error != 0) {
+ /*
+ * mpt_scsi_send_tmf hard resets on failure, so no
+ * need to do so here. Our queue should be emptied
+ * by the hard reset.
+ */
+ continue;
+ }
+
+ error = mpt_wait_req(mpt, mpt->tmf_req, REQ_STATE_DONE,
+ REQ_STATE_DONE, /*sleep_ok*/TRUE, /*time_ms*/5000);
+
+ status = mpt->tmf_req->IOCStatus;
+ if (error != 0) {
+
+ /*
+ * If we've errored out and the transaction is still
+ * pending, reset the controller.
+ */
+ mpt_prt(mpt, "mpt_recover_commands: Abort timed-out."
+ "Resetting controller\n");
+ mpt_reset(mpt, /*reinit*/TRUE);
+ continue;
+ }
+
+ /*
+ * TMF is complete.
+ */
+ mpt->tmf_req->state = REQ_STATE_FREE;
+ if ((status & MPI_IOCSTATUS_MASK) == MPI_SCSI_STATUS_SUCCESS)
+ continue;
+
+ mpt_lprt(mpt, MPT_PRT_DEBUG,
+ "mpt_recover_commands: Abort Failed "
+ "with status 0x%x\n. Resetting bus", status);
+
+ /*
+ * If the abort attempt fails for any reason, reset the bus.
+ * We should find all of the timed-out commands on our
+ * list are in the done state after this completes.
+ */
+ mpt_bus_reset(mpt, /*sleep_ok*/TRUE);
+ }
+
+ MPT_UNLOCK(mpt);
+}
OpenPOWER on IntegriCloud