b0a2fdee0d
o Add timeout error recovery (from a thread context to avoid the deferral of other critical interrupts). o Properly recover commands across controller reset events. o Update the driver to handle events and status codes that have been added to the MPI spec since the driver was originally written. o Make the driver more modular to improve maintainability and support dynamic "personality" registration (e.g. SCSI Initiator, RAID, SAS, FC, etc). o Shorten and simplify the common I/O path to improve driver performance. o Add RAID volume and RAID member state/settings reporting. o Add periodic volume resynchronization status reporting. o Add support for sysctl tunable resync rate, member write cache enable, and volume transaction queue depth. Sponsored by ---------------- Avid Technologies Inc: SCSI error recovery, driver re-organization, update of MPI library headers, portions of dynamic personality registration, and misc bug fixes. Wheel Open Technologies: RAID event notification, RAID member pass-thru support, firmware upload/download support, enhanced RAID resync speed, portions of dynamic personality registration, and misc bug fixes. Detailed Changes ================ mpt.c mpt_cam.c mpt_raid.c mpt_pci.c: o Add support for personality modules. Each module exports load, and unload module scope methods as well as probe, attach, event, reset, shutdown, and detach per-device instance methods mpt.c mpt.h mpt_pci.c: o The driver now associates a callback function (via an index) with every transaction submitted to the controller. This allows the main interrupt handler to absolve itself of any knowledge of individual transaction/response types by simply calling the callback function "registered" for the transaction. We use a callback index instead of a callback function pointer in each requests so we can properly handle responses (e.g. event notifications) that are not associated with a transaction. Personality modules dynamically register their callbacks with the driver core to receive the callback index to use for their handlers. o Move the interrupt handler into mpt.c. The ISR algorithm is bus transport and OS independent and thus had no reason to be in mpt_pci.c. o Simplify configuration message reply handling by copying reply frame data for the requester and storing completion status in the original request structure. o Add the mpt_complete_request_chain() helper method and use it to implement reset handlers that must abort transactions. o Keep track of all pending requests on the new requests_pending_list in the softc. o Add default handlers to mpt.c to handle generic event notifications and controller reset activities. The event handler code is largely the same as in the original driver. The reset handler is new and terminates any pending transactions with a status code indicating the controller needs to be re-initialized. o Add some endian support to the driver. A complete audit is still required for this driver to have any hope of operating in a big-endian environment. o Use inttypes.h and __inline. Come closer to being style(9) compliant. o Remove extraneous use of typedefs. o Convert request state from a strict enumeration to a series of flags. This allows us to, for example, tag transactions that have timed-out while retaining the state that the transaction is still in-flight on the controller. o Add mpt_wait_req() which allows a caller to poll or sleep for the completion of a request. Use this to simplify and factor code out from many initialization routines. We also use this to sleep for task management request completions in our CAM timeout handler. mpt.c: o Correct a bug in the event handler where request structures were freed even if the request reply was marked as a continuation reply. Continuation replies indicate that the controller still owns the request and freeing these replies prematurely corrupted controller state. o Implement firmware upload and download. On controllers that do not have dedicated NVRAM (as in the Sun v20/v40z), the firmware image is downloaded to the controller by the system BIOS. This image occupies precious controller RAM space until the host driver fetches the image, reducing the number of concurrent I/Os the controller can processes. The uploaded image is used to re-program the controller during hard reset events since the controller cannot fetch the firmware on its own. Implementing this feature allows much higher queue depths when RAID volumes are configured. o Changed configuration page accessors to allow threads to sleep rather than busy wait for completion. o Removed hard coded data transfer sizes from configuration page routines so that RAID configuration page processing is possible. mpt_reg.h: o Move controller register definitions into a separate file. mpt.h: o Re-arrange includes to allow inlined functions to be defined in mpt.h. o Add reply, event, and reset handler definitions. o Add softc fields for handling timeout and controller reset recovery. mpt_cam.c: o Move mpt_freebsd.c to mpt_cam.c. Move all core functionality, such as event handling, into mpt.c leaving only CAM SCSI support here. o Revamp completion handler to provide correct CAM status for all currently defined SCSI MPI message result codes. o Register event and reset handlers with the MPT core. Modify the event handler to notify CAM of bus reset events. The controller reset handler will abort any transactions that have timed out. All other pending CAM transactions are correctly aborted by the core driver's reset handler. o Allocate a single request up front to perform task management operations. This guarantees that we can always perform a TMF operation even when the controller is saturated with other operations. The single request also serves as a perfect mechanism of guaranteeing that only a single TMF is in flight at a time - something that is required according to the MPT Fusion documentation. o Add a helper function for issuing task management requests to the controller. This is used to abort individual requests or perform a bus reset. o Modify the CAM XPT_BUS_RESET ccb handler to wait for and properly handle the status of the bus reset task management frame used to reset the bus. The previous code assumed that the reset request would always succeed. o Add timeout recovery support. When a timeout occurs, the timed-out request is added to a queue to be processed by our recovery thread and the thread is woken up. The recovery thread processes timed-out command serially, attempting first to abort them and then falling back to a bus reset if an abort fails. o Add calls to mpt_reset() to reset the controller if any handshake command, bus reset attempt or abort attempt fails due to a timeout. o Export a secondary "bus" to CAM that exposes all volume drive members as pass-thru devices, allowing CAM to perform proper speed negotiation to hidden devices. o Add a CAM async event handler tracking the AC_FOUND_DEVICE event. Use this to trigger calls to set the per-volume queue depth once the volume is fully registered with CAM. This is required to avoid hitting firmware limits on volume queue depth. Exceeding the limit causes the firmware to hang. mpt_cam.h: o Add several helper functions for interfacing to CAM and performing timeout recovery. mpt_pci.c: o Disable interrupts on the controller before registering and enabling interrupt delivery to the OS. Otherwise we risk receiving interrupts before the driver is ready to receive them. o Make use of compatibility macros that allow the driver to be compiled under 4.x and 5.x. mpt_raid.c: o Add a per-controller instance RAID thread to perform settings changes and query status (minimizes CPU busy wait loops). o Use a shutdown handler to disable "Member Write Cache Enable" (MWCE) setting for RAID arrays set to enable MWCE During Rebuild. o Change reply handler function signature to allow handlers to defer the deletion of reply frames. Use this to allow the event reply handler to queue up events that need to be acked if no resources are available to immediately ack an event. Queued events are processed in mpt_free_request() where resources are freed. This avoids a panic on resource shortage. o Parse and print out RAID controller capabilities during driver probe. o Define, allocate, and maintain RAID data structures for volumes, hidden member physical disks and spare disks. o Add dynamic sysctls for per-instance setting of the log level, array resync rate, array member cache enable, and volume queue depth. mpt_debug.c: o Add mpt_lprt and mpt_lprtc for printing diagnostics conditioned on a particular log level to aid in tracking down driver issues. o Add mpt_decode_value() which parses the bits in an integer value based on a parsing table (mask, value, name string, tuples). mpilib/*: o Update mpi library header files to latest distribution from LSI. Submitted by: gibbs Approved by: re
1932 lines
53 KiB
C
1932 lines
53 KiB
C
/*-
|
|
* 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);
|
|
}
|