/*-
 * Routines for handling the integrated RAID features LSI MPT Fusion adapters.
 *
 * Copyright (c) 2005, WHEEL Sp. z o.o.
 * Copyright (c) 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 names of the above listed copyright holders nor the names
 *    of any 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.
 */
/*-
 * Some Breakage and Bug Fixing added later.
 * Copyright (c) 2006, by Matthew Jacob
 * All Rights Reserved
 *
 * Support from LSI-Logic has also gone a great deal toward making this a
 * workable subsystem and is gratefully acknowledged.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: releng/9.2/sys/dev/mpt/mpt_raid.c 253037 2013-07-08 15:54:38Z mav $");

#include <dev/mpt/mpt.h>
#include <dev/mpt/mpt_raid.h>

#include "dev/mpt/mpilib/mpi_ioc.h" /* XXX Fix Event Handling!!! */
#include "dev/mpt/mpilib/mpi_raid.h"

#include <cam/cam.h>
#include <cam/cam_ccb.h>
#include <cam/cam_periph.h>
#include <cam/cam_sim.h>
#include <cam/cam_xpt_sim.h>

#include <sys/callout.h>
#include <sys/kthread.h>
#include <sys/sysctl.h>

#include <machine/stdarg.h>

struct mpt_raid_action_result
{
	union {
		MPI_RAID_VOL_INDICATOR	indicator_struct;
		uint32_t		new_settings;
		uint8_t			phys_disk_num;
	} action_data;
	uint16_t			action_status;
};

#define REQ_TO_RAID_ACTION_RESULT(req) ((struct mpt_raid_action_result *) \
	(((MSG_RAID_ACTION_REQUEST *)(req->req_vbuf)) + 1))

#define REQ_IOCSTATUS(req) ((req)->IOCStatus & MPI_IOCSTATUS_MASK)

static mpt_probe_handler_t	mpt_raid_probe;
static mpt_attach_handler_t	mpt_raid_attach;
static mpt_enable_handler_t	mpt_raid_enable;
static mpt_event_handler_t	mpt_raid_event;
static mpt_shutdown_handler_t	mpt_raid_shutdown;
static mpt_reset_handler_t	mpt_raid_ioc_reset;
static mpt_detach_handler_t	mpt_raid_detach;

static struct mpt_personality mpt_raid_personality =
{
	.name		= "mpt_raid",
	.probe		= mpt_raid_probe,
	.attach		= mpt_raid_attach,
	.enable		= mpt_raid_enable,
	.event		= mpt_raid_event,
	.reset		= mpt_raid_ioc_reset,
	.shutdown	= mpt_raid_shutdown,
	.detach		= mpt_raid_detach,
};

DECLARE_MPT_PERSONALITY(mpt_raid, SI_ORDER_THIRD);
MPT_PERSONALITY_DEPEND(mpt_raid, mpt_cam, 1, 1, 1);

static mpt_reply_handler_t mpt_raid_reply_handler;
static int mpt_raid_reply_frame_handler(struct mpt_softc *mpt, request_t *req,
					MSG_DEFAULT_REPLY *reply_frame);
static int mpt_spawn_raid_thread(struct mpt_softc *mpt);
static void mpt_terminate_raid_thread(struct mpt_softc *mpt);
static void mpt_raid_thread(void *arg);
static timeout_t mpt_raid_timer;
#if 0
static void mpt_enable_vol(struct mpt_softc *mpt,
			   struct mpt_raid_volume *mpt_vol, int enable);
#endif
static void mpt_verify_mwce(struct mpt_softc *, struct mpt_raid_volume *);
static void mpt_adjust_queue_depth(struct mpt_softc *, struct mpt_raid_volume *,
    struct cam_path *);
static void mpt_raid_sysctl_attach(struct mpt_softc *);

static const char *mpt_vol_type(struct mpt_raid_volume *vol);
static const char *mpt_vol_state(struct mpt_raid_volume *vol);
static const char *mpt_disk_state(struct mpt_raid_disk *disk);
static void mpt_vol_prt(struct mpt_softc *mpt, struct mpt_raid_volume *vol,
    const char *fmt, ...);
static void mpt_disk_prt(struct mpt_softc *mpt, struct mpt_raid_disk *disk,
    const char *fmt, ...);

static int mpt_issue_raid_req(struct mpt_softc *mpt,
    struct mpt_raid_volume *vol, struct mpt_raid_disk *disk, request_t *req,
    u_int Action, uint32_t ActionDataWord, bus_addr_t addr, bus_size_t len,
    int write, int wait);

static int mpt_refresh_raid_data(struct mpt_softc *mpt);
static void mpt_schedule_raid_refresh(struct mpt_softc *mpt);

static uint32_t raid_handler_id = MPT_HANDLER_ID_NONE;

static const char *
mpt_vol_type(struct mpt_raid_volume *vol)
{
	switch (vol->config_page->VolumeType) {
	case MPI_RAID_VOL_TYPE_IS:
		return ("RAID-0");
	case MPI_RAID_VOL_TYPE_IME:
		return ("RAID-1E");
	case MPI_RAID_VOL_TYPE_IM:
		return ("RAID-1");
	default:
		return ("Unknown");
	}
}

static const char *
mpt_vol_state(struct mpt_raid_volume *vol)
{
	switch (vol->config_page->VolumeStatus.State) {
	case MPI_RAIDVOL0_STATUS_STATE_OPTIMAL:
		return ("Optimal");
	case MPI_RAIDVOL0_STATUS_STATE_DEGRADED:
		return ("Degraded");
	case MPI_RAIDVOL0_STATUS_STATE_FAILED:
		return ("Failed");
	default:
		return ("Unknown");
	}
}

static const char *
mpt_disk_state(struct mpt_raid_disk *disk)
{
	switch (disk->config_page.PhysDiskStatus.State) {
	case MPI_PHYSDISK0_STATUS_ONLINE:
		return ("Online");
	case MPI_PHYSDISK0_STATUS_MISSING:
		return ("Missing");
	case MPI_PHYSDISK0_STATUS_NOT_COMPATIBLE:
		return ("Incompatible");
	case MPI_PHYSDISK0_STATUS_FAILED:
		return ("Failed");
	case MPI_PHYSDISK0_STATUS_INITIALIZING:
		return ("Initializing");
	case MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED:
		return ("Offline Requested");
	case MPI_PHYSDISK0_STATUS_FAILED_REQUESTED:
		return ("Failed per Host Request");
	case MPI_PHYSDISK0_STATUS_OTHER_OFFLINE:
		return ("Offline");
	default:
		return ("Unknown");
	}
}

static void
mpt_vol_prt(struct mpt_softc *mpt, struct mpt_raid_volume *vol,
	    const char *fmt, ...)
{
	va_list ap;

	printf("%s:vol%d(%s:%d:%d): ", device_get_nameunit(mpt->dev),
	       (u_int)(vol - mpt->raid_volumes), device_get_nameunit(mpt->dev),
	       vol->config_page->VolumeBus, vol->config_page->VolumeID);
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
}

static void
mpt_disk_prt(struct mpt_softc *mpt, struct mpt_raid_disk *disk,
	     const char *fmt, ...)
{
	va_list ap;

	if (disk->volume != NULL) {
		printf("(%s:vol%d:%d): ",
		       device_get_nameunit(mpt->dev),
		       disk->volume->config_page->VolumeID,
		       disk->member_number);
	} else {
		printf("(%s:%d:%d): ", device_get_nameunit(mpt->dev),
		       disk->config_page.PhysDiskBus,
		       disk->config_page.PhysDiskID);
	}
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
}

static void
mpt_raid_async(void *callback_arg, u_int32_t code,
	       struct cam_path *path, void *arg)
{
	struct mpt_softc *mpt;

	mpt = (struct mpt_softc*)callback_arg;
	switch (code) {
	case AC_FOUND_DEVICE:
	{
		struct ccb_getdev *cgd;
		struct mpt_raid_volume *mpt_vol;

		cgd = (struct ccb_getdev *)arg;
		if (cgd == NULL) {
			break;
		}

		mpt_lprt(mpt, MPT_PRT_DEBUG, "Callback for %d\n",
			 cgd->ccb_h.target_id);
		
		RAID_VOL_FOREACH(mpt, mpt_vol) {
			if ((mpt_vol->flags & MPT_RVF_ACTIVE) == 0)
				continue;

			if (mpt_vol->config_page->VolumeID 
			 == cgd->ccb_h.target_id) {
				mpt_adjust_queue_depth(mpt, mpt_vol, path);
				break;
			}
		}
	}
	default:
		break;
	}
}

static int
mpt_raid_probe(struct mpt_softc *mpt)
{

	if (mpt->ioc_page2 == NULL || mpt->ioc_page2->MaxPhysDisks == 0) {
		return (ENODEV);
	}
	return (0);
}

static int
mpt_raid_attach(struct mpt_softc *mpt)
{
	struct ccb_setasync csa;
	mpt_handler_t	 handler;
	int		 error;

	mpt_callout_init(mpt, &mpt->raid_timer);

	error = mpt_spawn_raid_thread(mpt);
	if (error != 0) {
		mpt_prt(mpt, "Unable to spawn RAID thread!\n");
		goto cleanup;
	}
 
	MPT_LOCK(mpt);
	handler.reply_handler = mpt_raid_reply_handler;
	error = mpt_register_handler(mpt, MPT_HANDLER_REPLY, handler,
				     &raid_handler_id);
	if (error != 0) {
		mpt_prt(mpt, "Unable to register RAID haandler!\n");
		goto cleanup;
	}

	xpt_setup_ccb(&csa.ccb_h, mpt->path, 5);
	csa.ccb_h.func_code = XPT_SASYNC_CB;
	csa.event_enable = AC_FOUND_DEVICE;
	csa.callback = mpt_raid_async;
	csa.callback_arg = mpt;
	xpt_action((union ccb *)&csa);
	if (csa.ccb_h.status != CAM_REQ_CMP) {
		mpt_prt(mpt, "mpt_raid_attach: Unable to register "
			"CAM async handler.\n");
	}
	MPT_UNLOCK(mpt);

	mpt_raid_sysctl_attach(mpt);
	return (0);
cleanup:
	MPT_UNLOCK(mpt);
	mpt_raid_detach(mpt);
	return (error);
}

static int
mpt_raid_enable(struct mpt_softc *mpt)
{

	return (0);
}

static void
mpt_raid_detach(struct mpt_softc *mpt)
{
	struct ccb_setasync csa;
	mpt_handler_t handler;

	mpt_callout_drain(mpt, &mpt->raid_timer);

	MPT_LOCK(mpt);
	mpt_terminate_raid_thread(mpt); 
	handler.reply_handler = mpt_raid_reply_handler;
	mpt_deregister_handler(mpt, MPT_HANDLER_REPLY, handler,
			       raid_handler_id);
	xpt_setup_ccb(&csa.ccb_h, mpt->path, /*priority*/5);
	csa.ccb_h.func_code = XPT_SASYNC_CB;
	csa.event_enable = 0;
	csa.callback = mpt_raid_async;
	csa.callback_arg = mpt;
	xpt_action((union ccb *)&csa);
	MPT_UNLOCK(mpt);
}

static void
mpt_raid_ioc_reset(struct mpt_softc *mpt, int type)
{

	/* Nothing to do yet. */
}

static const char *raid_event_txt[] =
{
	"Volume Created",
	"Volume Deleted",
	"Volume Settings Changed",
	"Volume Status Changed",
	"Volume Physical Disk Membership Changed",
	"Physical Disk Created",
	"Physical Disk Deleted",
	"Physical Disk Settings Changed",
	"Physical Disk Status Changed",
	"Domain Validation Required",
	"SMART Data Received",
	"Replace Action Started",
};

static int
mpt_raid_event(struct mpt_softc *mpt, request_t *req,
	       MSG_EVENT_NOTIFY_REPLY *msg)
{
	EVENT_DATA_RAID *raid_event;
	struct mpt_raid_volume *mpt_vol;
	struct mpt_raid_disk *mpt_disk;
	CONFIG_PAGE_RAID_VOL_0 *vol_pg;
	int i;
	int print_event;

	if (msg->Event != MPI_EVENT_INTEGRATED_RAID) {
		return (0);
	}

	raid_event = (EVENT_DATA_RAID *)&msg->Data;

	mpt_vol = NULL;
	vol_pg = NULL;
	if (mpt->raid_volumes != NULL && mpt->ioc_page2 != NULL) {
		for (i = 0; i < mpt->ioc_page2->MaxVolumes; i++) {
			mpt_vol = &mpt->raid_volumes[i];
			vol_pg = mpt_vol->config_page;

			if ((mpt_vol->flags & MPT_RVF_ACTIVE) == 0)
				continue;

			if (vol_pg->VolumeID == raid_event->VolumeID
			 && vol_pg->VolumeBus == raid_event->VolumeBus)
				break;
		}
		if (i >= mpt->ioc_page2->MaxVolumes) {
			mpt_vol = NULL;
			vol_pg = NULL;
		}
	}

	mpt_disk = NULL;
	if (raid_event->PhysDiskNum != 0xFF && mpt->raid_disks != NULL) {
		mpt_disk = mpt->raid_disks + raid_event->PhysDiskNum;
		if ((mpt_disk->flags & MPT_RDF_ACTIVE) == 0) {
			mpt_disk = NULL;
		}
	}

	print_event = 1;
	switch(raid_event->ReasonCode) {
	case MPI_EVENT_RAID_RC_VOLUME_CREATED:
	case MPI_EVENT_RAID_RC_VOLUME_DELETED:
		break;
	case MPI_EVENT_RAID_RC_VOLUME_STATUS_CHANGED:
		if (mpt_vol != NULL) {
			if ((mpt_vol->flags & MPT_RVF_UP2DATE) != 0) {
				mpt_vol->flags &= ~MPT_RVF_UP2DATE;
			} else {
				/*
				 * Coalesce status messages into one
				 * per background run of our RAID thread.
				 * This removes "spurious" status messages
				 * from our output.
				 */
				print_event = 0;
			}
		}
		break;
	case MPI_EVENT_RAID_RC_VOLUME_SETTINGS_CHANGED:
	case MPI_EVENT_RAID_RC_VOLUME_PHYSDISK_CHANGED:
		mpt->raid_rescan++;
		if (mpt_vol != NULL) {
			mpt_vol->flags &= ~(MPT_RVF_UP2DATE|MPT_RVF_ANNOUNCED);
		}
		break;
	case MPI_EVENT_RAID_RC_PHYSDISK_CREATED:
	case MPI_EVENT_RAID_RC_PHYSDISK_DELETED:
		mpt->raid_rescan++;
		break;
	case MPI_EVENT_RAID_RC_PHYSDISK_SETTINGS_CHANGED:
	case MPI_EVENT_RAID_RC_PHYSDISK_STATUS_CHANGED:
		mpt->raid_rescan++;
		if (mpt_disk != NULL) {
			mpt_disk->flags &= ~MPT_RDF_UP2DATE;
		}
		break;
	case MPI_EVENT_RAID_RC_DOMAIN_VAL_NEEDED:
		mpt->raid_rescan++;
		break;
	case MPI_EVENT_RAID_RC_SMART_DATA:
	case MPI_EVENT_RAID_RC_REPLACE_ACTION_STARTED:
		break;
	}

	if (print_event) {
		if (mpt_disk != NULL) {
			mpt_disk_prt(mpt, mpt_disk, "");
		} else if (mpt_vol != NULL) {
			mpt_vol_prt(mpt, mpt_vol, "");
		} else {
			mpt_prt(mpt, "Volume(%d:%d", raid_event->VolumeBus,
				raid_event->VolumeID);

			if (raid_event->PhysDiskNum != 0xFF)
				mpt_prtc(mpt, ":%d): ",
					 raid_event->PhysDiskNum);
			else
				mpt_prtc(mpt, "): ");
		}

		if (raid_event->ReasonCode >= NUM_ELEMENTS(raid_event_txt))
			mpt_prtc(mpt, "Unhandled RaidEvent %#x\n",
				 raid_event->ReasonCode);
		else
			mpt_prtc(mpt, "%s\n",
				 raid_event_txt[raid_event->ReasonCode]);
	}

	if (raid_event->ReasonCode == MPI_EVENT_RAID_RC_SMART_DATA) {
		/* XXX Use CAM's print sense for this... */
		if (mpt_disk != NULL)
			mpt_disk_prt(mpt, mpt_disk, "");
		else
			mpt_prt(mpt, "Volume(%d:%d:%d: ",
			    raid_event->VolumeBus, raid_event->VolumeID,
			    raid_event->PhysDiskNum);
		mpt_prtc(mpt, "ASC 0x%x, ASCQ 0x%x)\n",
			 raid_event->ASC, raid_event->ASCQ);
	}

	mpt_raid_wakeup(mpt);
	return (1);
}

static void
mpt_raid_shutdown(struct mpt_softc *mpt)
{
	struct mpt_raid_volume *mpt_vol;

	if (mpt->raid_mwce_setting != MPT_RAID_MWCE_REBUILD_ONLY) {
		return;
	}

	mpt->raid_mwce_setting = MPT_RAID_MWCE_OFF;
	RAID_VOL_FOREACH(mpt, mpt_vol) {
		mpt_verify_mwce(mpt, mpt_vol);
	}
}

static int
mpt_raid_reply_handler(struct mpt_softc *mpt, request_t *req,
    uint32_t reply_desc, MSG_DEFAULT_REPLY *reply_frame)
{
	int free_req;

	if (req == NULL)
		return (TRUE);

	free_req = TRUE;
	if (reply_frame != NULL)
		free_req = mpt_raid_reply_frame_handler(mpt, req, reply_frame);
#ifdef NOTYET
	else if (req->ccb != NULL) {
		/* Complete Quiesce CCB with error... */
	}
#endif

	req->state &= ~REQ_STATE_QUEUED;
	req->state |= REQ_STATE_DONE;
	TAILQ_REMOVE(&mpt->request_pending_list, req, links);

	if ((req->state & REQ_STATE_NEED_WAKEUP) != 0) {
		wakeup(req);
	} else if (free_req) {
		mpt_free_request(mpt, req);
	}

	return (TRUE);
}

/*
 * Parse additional completion information in the reply
 * frame for RAID I/O requests.
 */
static int
mpt_raid_reply_frame_handler(struct mpt_softc *mpt, request_t *req,
    MSG_DEFAULT_REPLY *reply_frame)
{
	MSG_RAID_ACTION_REPLY *reply;
	struct mpt_raid_action_result *action_result;
	MSG_RAID_ACTION_REQUEST *rap;

	reply = (MSG_RAID_ACTION_REPLY *)reply_frame;
	req->IOCStatus = le16toh(reply->IOCStatus);
	rap = (MSG_RAID_ACTION_REQUEST *)req->req_vbuf;
	
	switch (rap->Action) {
	case MPI_RAID_ACTION_QUIESCE_PHYS_IO:
		mpt_prt(mpt, "QUIESCE PHYSIO DONE\n");
		break;
	case MPI_RAID_ACTION_ENABLE_PHYS_IO:
		mpt_prt(mpt, "ENABLY PHYSIO DONE\n");
		break;
	default:
		break;
	}
	action_result = REQ_TO_RAID_ACTION_RESULT(req);
	memcpy(&action_result->action_data, &reply->ActionData,
	    sizeof(action_result->action_data));
	action_result->action_status = le16toh(reply->ActionStatus);
	return (TRUE);
}

/*
 * Utiltity routine to perform a RAID action command;
 */
static int
mpt_issue_raid_req(struct mpt_softc *mpt, struct mpt_raid_volume *vol,
		   struct mpt_raid_disk *disk, request_t *req, u_int Action,
		   uint32_t ActionDataWord, bus_addr_t addr, bus_size_t len,
		   int write, int wait)
{
	MSG_RAID_ACTION_REQUEST *rap;
	SGE_SIMPLE32 *se;

	rap = req->req_vbuf;
	memset(rap, 0, sizeof *rap);
	rap->Action = Action;
	rap->ActionDataWord = htole32(ActionDataWord);
	rap->Function = MPI_FUNCTION_RAID_ACTION;
	rap->VolumeID = vol->config_page->VolumeID;
	rap->VolumeBus = vol->config_page->VolumeBus;
	if (disk != 0)
		rap->PhysDiskNum = disk->config_page.PhysDiskNum;
	else
		rap->PhysDiskNum = 0xFF;
	se = (SGE_SIMPLE32 *)&rap->ActionDataSGE;
	se->Address = htole32(addr);
	MPI_pSGE_SET_LENGTH(se, len);
	MPI_pSGE_SET_FLAGS(se, (MPI_SGE_FLAGS_SIMPLE_ELEMENT |
	    MPI_SGE_FLAGS_LAST_ELEMENT | MPI_SGE_FLAGS_END_OF_BUFFER |
	    MPI_SGE_FLAGS_END_OF_LIST |
	    (write ? MPI_SGE_FLAGS_HOST_TO_IOC : MPI_SGE_FLAGS_IOC_TO_HOST)));
	se->FlagsLength = htole32(se->FlagsLength);
	rap->MsgContext = htole32(req->index | raid_handler_id);

	mpt_check_doorbell(mpt);
	mpt_send_cmd(mpt, req);

	if (wait) {
		return (mpt_wait_req(mpt, req, REQ_STATE_DONE, REQ_STATE_DONE,
				     /*sleep_ok*/FALSE, /*time_ms*/2000));
	} else {
		return (0);
	}
}

/*************************** RAID Status Monitoring ***************************/
static int
mpt_spawn_raid_thread(struct mpt_softc *mpt)
{
	int error;

	/*
	 * Freeze out any CAM transactions until our thread
	 * is able to run at least once.  We need to update
	 * our RAID pages before acception I/O or we may
	 * reject I/O to an ID we later determine is for a
	 * hidden physdisk.
	 */
	MPT_LOCK(mpt);
	xpt_freeze_simq(mpt->phydisk_sim, 1);
	MPT_UNLOCK(mpt);
	error = mpt_kthread_create(mpt_raid_thread, mpt,
	    &mpt->raid_thread, /*flags*/0, /*altstack*/0,
	    "mpt_raid%d", mpt->unit);
	if (error != 0) {
		MPT_LOCK(mpt);
		xpt_release_simq(mpt->phydisk_sim, /*run_queue*/FALSE);
		MPT_UNLOCK(mpt);
	}
	return (error);
}

static void
mpt_terminate_raid_thread(struct mpt_softc *mpt)
{

	if (mpt->raid_thread == NULL) {
		return;
	}
	mpt->shutdwn_raid = 1;
	wakeup(&mpt->raid_volumes);
	/*
	 * Sleep on a slightly different location
	 * for this interlock just for added safety.
	 */
	mpt_sleep(mpt, &mpt->raid_thread, PUSER, "thtrm", 0);
}

static void
mpt_raid_thread(void *arg)
{
	struct mpt_softc *mpt;
	int firstrun;

	mpt = (struct mpt_softc *)arg;
	firstrun = 1;
	MPT_LOCK(mpt);
	while (mpt->shutdwn_raid == 0) {

		if (mpt->raid_wakeup == 0) {
			mpt_sleep(mpt, &mpt->raid_volumes, PUSER, "idle", 0);
			continue;
		}

		mpt->raid_wakeup = 0;

		if (mpt_refresh_raid_data(mpt)) {
			mpt_schedule_raid_refresh(mpt);	/* XX NOT QUITE RIGHT */
			continue;
		}

		/*
		 * Now that we have our first snapshot of RAID data,
		 * allow CAM to access our physical disk bus.
		 */
		if (firstrun) {
			firstrun = 0;
			xpt_release_simq(mpt->phydisk_sim, TRUE);
		}

		if (mpt->raid_rescan != 0) {
			union ccb *ccb;
			int error;

			mpt->raid_rescan = 0;
			MPT_UNLOCK(mpt);

			ccb = xpt_alloc_ccb();

			MPT_LOCK(mpt);
			error = xpt_create_path(&ccb->ccb_h.path, NULL,
			    cam_sim_path(mpt->phydisk_sim),
			    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
			if (error != CAM_REQ_CMP) {
				xpt_free_ccb(ccb);
				mpt_prt(mpt, "Unable to rescan RAID Bus!\n");
			} else {
				xpt_rescan(ccb);
			}
		}
	}
	mpt->raid_thread = NULL;
	wakeup(&mpt->raid_thread);
	MPT_UNLOCK(mpt);
	mpt_kthread_exit(0);
}

#if 0
static void
mpt_raid_quiesce_timeout(void *arg)
{

	/* Complete the CCB with error */
	/* COWWWW */
}

static timeout_t mpt_raid_quiesce_timeout;
cam_status
mpt_raid_quiesce_disk(struct mpt_softc *mpt, struct mpt_raid_disk *mpt_disk,
		      request_t *req)
{
	union ccb *ccb;

	ccb = req->ccb;
	if ((mpt_disk->flags & MPT_RDF_QUIESCED) != 0)
		return (CAM_REQ_CMP);

	if ((mpt_disk->flags & MPT_RDF_QUIESCING) == 0) {
		int rv;

		mpt_disk->flags |= MPT_RDF_QUIESCING;
		xpt_freeze_devq(ccb->ccb_h.path, 1);
		
		rv = mpt_issue_raid_req(mpt, mpt_disk->volume, mpt_disk, req,
					MPI_RAID_ACTION_QUIESCE_PHYS_IO,
					/*ActionData*/0, /*addr*/0,
					/*len*/0, /*write*/FALSE,
					/*wait*/FALSE);
		if (rv != 0)
			return (CAM_REQ_CMP_ERR);

		mpt_req_timeout(req, mpt_raid_quiesce_timeout, ccb, 5 * hz);
#if 0
		if (rv == ETIMEDOUT) {
			mpt_disk_prt(mpt, mpt_disk, "mpt_raid_quiesce_disk: "
				     "Quiece Timed-out\n");
			xpt_release_devq(ccb->ccb_h.path, 1, /*run*/0);
			return (CAM_REQ_CMP_ERR);
		}

		ar = REQ_TO_RAID_ACTION_RESULT(req);
		if (rv != 0
		 || REQ_IOCSTATUS(req) != MPI_IOCSTATUS_SUCCESS
		 || (ar->action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS)) {
			mpt_disk_prt(mpt, mpt_disk, "Quiece Failed"
				    "%d:%x:%x\n", rv, req->IOCStatus,
				    ar->action_status);
			xpt_release_devq(ccb->ccb_h.path, 1, /*run*/0);
			return (CAM_REQ_CMP_ERR);
		}
#endif
		return (CAM_REQ_INPROG);
	}
	return (CAM_REQUEUE_REQ);
}
#endif

/* XXX Ignores that there may be multiple busses/IOCs involved. */
cam_status
mpt_map_physdisk(struct mpt_softc *mpt, union ccb *ccb, target_id_t *tgt)
{
	struct mpt_raid_disk *mpt_disk;

	mpt_disk = mpt->raid_disks + ccb->ccb_h.target_id;
	if (ccb->ccb_h.target_id < mpt->raid_max_disks
	 && (mpt_disk->flags & MPT_RDF_ACTIVE) != 0) {
		*tgt = mpt_disk->config_page.PhysDiskID;
		return (0);
	}
	mpt_lprt(mpt, MPT_PRT_DEBUG1, "mpt_map_physdisk(%d) - Not Active\n",
		 ccb->ccb_h.target_id);
	return (-1);
}

/* XXX Ignores that there may be multiple busses/IOCs involved. */
int
mpt_is_raid_member(struct mpt_softc *mpt, target_id_t tgt)
{
	struct mpt_raid_disk *mpt_disk;
	int i;

	if (mpt->ioc_page2 == NULL || mpt->ioc_page2->MaxPhysDisks == 0)
		return (0);
	for (i = 0; i < mpt->ioc_page2->MaxPhysDisks; i++) {
		mpt_disk = &mpt->raid_disks[i];
		if ((mpt_disk->flags & MPT_RDF_ACTIVE) != 0 &&
		    mpt_disk->config_page.PhysDiskID == tgt)
			return (1);
	}
	return (0);
	
}

/* XXX Ignores that there may be multiple busses/IOCs involved. */
int
mpt_is_raid_volume(struct mpt_softc *mpt, target_id_t tgt)
{
	CONFIG_PAGE_IOC_2_RAID_VOL *ioc_vol;
	CONFIG_PAGE_IOC_2_RAID_VOL *ioc_last_vol;

	if (mpt->ioc_page2 == NULL || mpt->ioc_page2->MaxPhysDisks == 0) {
		return (0);
	}
	ioc_vol = mpt->ioc_page2->RaidVolume;
	ioc_last_vol = ioc_vol + mpt->ioc_page2->NumActiveVolumes;
	for (;ioc_vol != ioc_last_vol; ioc_vol++) {
		if (ioc_vol->VolumeID == tgt) {
			return (1);
		}
	}
	return (0);
}

#if 0
static void
mpt_enable_vol(struct mpt_softc *mpt, struct mpt_raid_volume *mpt_vol,
	       int enable)
{
	request_t *req;
	struct mpt_raid_action_result *ar;
	CONFIG_PAGE_RAID_VOL_0 *vol_pg;
	int enabled;
	int rv;

	vol_pg = mpt_vol->config_page;
	enabled = vol_pg->VolumeStatus.Flags & MPI_RAIDVOL0_STATUS_FLAG_ENABLED;

	/*
	 * If the setting matches the configuration,
	 * there is nothing to do.
	 */
	if ((enabled && enable)
	 || (!enabled && !enable))
		return;

	req = mpt_get_request(mpt, /*sleep_ok*/TRUE);
	if (req == NULL) {
		mpt_vol_prt(mpt, mpt_vol,
			    "mpt_enable_vol: Get request failed!\n");
		return;
	}

	rv = mpt_issue_raid_req(mpt, mpt_vol, /*disk*/NULL, req,
				enable ? MPI_RAID_ACTION_ENABLE_VOLUME
				       : MPI_RAID_ACTION_DISABLE_VOLUME,
				/*data*/0, /*addr*/0, /*len*/0,
				/*write*/FALSE, /*wait*/TRUE);
	if (rv == ETIMEDOUT) {
		mpt_vol_prt(mpt, mpt_vol, "mpt_enable_vol: "
			    "%s Volume Timed-out\n",
			    enable ? "Enable" : "Disable");
		return;
	}
	ar = REQ_TO_RAID_ACTION_RESULT(req);
	if (rv != 0
	 || REQ_IOCSTATUS(req) != MPI_IOCSTATUS_SUCCESS
	 || (ar->action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS)) {
		mpt_vol_prt(mpt, mpt_vol, "%s Volume Failed: %d:%x:%x\n",
			    enable ? "Enable" : "Disable",
			    rv, req->IOCStatus, ar->action_status);
	}

	mpt_free_request(mpt, req);
}
#endif

static void
mpt_verify_mwce(struct mpt_softc *mpt, struct mpt_raid_volume *mpt_vol)
{
	request_t *req;
	struct mpt_raid_action_result *ar;
	CONFIG_PAGE_RAID_VOL_0 *vol_pg;
	uint32_t data;
	int rv;
	int resyncing;
	int mwce;

	vol_pg = mpt_vol->config_page;
	resyncing = vol_pg->VolumeStatus.Flags
		  & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS;
	mwce = vol_pg->VolumeSettings.Settings
	     & MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE;

	/*
	 * If the setting matches the configuration,
	 * there is nothing to do.
	 */
	switch (mpt->raid_mwce_setting) {
	case MPT_RAID_MWCE_REBUILD_ONLY:
		if ((resyncing && mwce) || (!resyncing && !mwce)) {
			return;
		}
		mpt_vol->flags ^= MPT_RVF_WCE_CHANGED;
		if ((mpt_vol->flags & MPT_RVF_WCE_CHANGED) == 0) {
			/*
			 * Wait one more status update to see if
			 * resyncing gets enabled.  It gets disabled
			 * temporarilly when WCE is changed.
			 */
			return;
		}
		break;
	case MPT_RAID_MWCE_ON:
		if (mwce)
			return;
		break;
	case MPT_RAID_MWCE_OFF:
		if (!mwce)
			return;
		break;
	case MPT_RAID_MWCE_NC:
		return;
	}

	req = mpt_get_request(mpt, /*sleep_ok*/TRUE);
	if (req == NULL) {
		mpt_vol_prt(mpt, mpt_vol,
			    "mpt_verify_mwce: Get request failed!\n");
		return;
	}

	vol_pg->VolumeSettings.Settings ^=
	    MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE;
	memcpy(&data, &vol_pg->VolumeSettings, sizeof(data));
	vol_pg->VolumeSettings.Settings ^=
	    MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE;
	rv = mpt_issue_raid_req(mpt, mpt_vol, /*disk*/NULL, req,
				MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS,
				data, /*addr*/0, /*len*/0,
				/*write*/FALSE, /*wait*/TRUE);
	if (rv == ETIMEDOUT) {
		mpt_vol_prt(mpt, mpt_vol, "mpt_verify_mwce: "
			    "Write Cache Enable Timed-out\n");
		return;
	}
	ar = REQ_TO_RAID_ACTION_RESULT(req);
	if (rv != 0
	 || REQ_IOCSTATUS(req) != MPI_IOCSTATUS_SUCCESS
	 || (ar->action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS)) {
		mpt_vol_prt(mpt, mpt_vol, "Write Cache Enable Failed: "
			    "%d:%x:%x\n", rv, req->IOCStatus,
			    ar->action_status);
	} else {
		vol_pg->VolumeSettings.Settings ^=
		    MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE;
	}
	mpt_free_request(mpt, req);
}

static void
mpt_verify_resync_rate(struct mpt_softc *mpt, struct mpt_raid_volume *mpt_vol)
{
	request_t *req;
	struct mpt_raid_action_result *ar;
	CONFIG_PAGE_RAID_VOL_0	*vol_pg;
	u_int prio;
	int rv;

	vol_pg = mpt_vol->config_page;

	if (mpt->raid_resync_rate == MPT_RAID_RESYNC_RATE_NC)
		return;

	/*
	 * If the current RAID resync rate does not
	 * match our configured rate, update it.
	 */
	prio = vol_pg->VolumeSettings.Settings
	     & MPI_RAIDVOL0_SETTING_PRIORITY_RESYNC;
	if (vol_pg->ResyncRate != 0
	 && vol_pg->ResyncRate != mpt->raid_resync_rate) {

		req = mpt_get_request(mpt, /*sleep_ok*/TRUE);
		if (req == NULL) {
			mpt_vol_prt(mpt, mpt_vol, "mpt_verify_resync_rate: "
				    "Get request failed!\n");
			return;
		}

		rv = mpt_issue_raid_req(mpt, mpt_vol, /*disk*/NULL, req,
					MPI_RAID_ACTION_SET_RESYNC_RATE,
					mpt->raid_resync_rate, /*addr*/0,
					/*len*/0, /*write*/FALSE, /*wait*/TRUE);
		if (rv == ETIMEDOUT) {
			mpt_vol_prt(mpt, mpt_vol, "mpt_refresh_raid_data: "
				    "Resync Rate Setting Timed-out\n");
			return;
		}

		ar = REQ_TO_RAID_ACTION_RESULT(req);
		if (rv != 0
		 || REQ_IOCSTATUS(req) != MPI_IOCSTATUS_SUCCESS
		 || (ar->action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS)) {
			mpt_vol_prt(mpt, mpt_vol, "Resync Rate Setting Failed: "
				    "%d:%x:%x\n", rv, req->IOCStatus,
				    ar->action_status);
		} else 
			vol_pg->ResyncRate = mpt->raid_resync_rate;
		mpt_free_request(mpt, req);
	} else if ((prio && mpt->raid_resync_rate < 128)
		|| (!prio && mpt->raid_resync_rate >= 128)) {
		uint32_t data;

		req = mpt_get_request(mpt, /*sleep_ok*/TRUE);
		if (req == NULL) {
			mpt_vol_prt(mpt, mpt_vol, "mpt_verify_resync_rate: "
				    "Get request failed!\n");
			return;
		}

		vol_pg->VolumeSettings.Settings ^=
		    MPI_RAIDVOL0_SETTING_PRIORITY_RESYNC;
		memcpy(&data, &vol_pg->VolumeSettings, sizeof(data));
		vol_pg->VolumeSettings.Settings ^=
		    MPI_RAIDVOL0_SETTING_PRIORITY_RESYNC;
		rv = mpt_issue_raid_req(mpt, mpt_vol, /*disk*/NULL, req,
					MPI_RAID_ACTION_CHANGE_VOLUME_SETTINGS,
					data, /*addr*/0, /*len*/0,
					/*write*/FALSE, /*wait*/TRUE);
		if (rv == ETIMEDOUT) {
			mpt_vol_prt(mpt, mpt_vol, "mpt_refresh_raid_data: "
				    "Resync Rate Setting Timed-out\n");
			return;
		}
		ar = REQ_TO_RAID_ACTION_RESULT(req);
		if (rv != 0
		 || REQ_IOCSTATUS(req) != MPI_IOCSTATUS_SUCCESS
		 || (ar->action_status != MPI_RAID_ACTION_ASTATUS_SUCCESS)) {
			mpt_vol_prt(mpt, mpt_vol, "Resync Rate Setting Failed: "
				    "%d:%x:%x\n", rv, req->IOCStatus,
				    ar->action_status);
		} else {
			vol_pg->VolumeSettings.Settings ^=
			    MPI_RAIDVOL0_SETTING_PRIORITY_RESYNC;
		}

		mpt_free_request(mpt, req);
	}
}

static void
mpt_adjust_queue_depth(struct mpt_softc *mpt, struct mpt_raid_volume *mpt_vol,
		       struct cam_path *path)
{
	struct ccb_relsim crs;

	xpt_setup_ccb(&crs.ccb_h, path, /*priority*/5);
	crs.ccb_h.func_code = XPT_REL_SIMQ;
	crs.ccb_h.flags = CAM_DEV_QFREEZE;
	crs.release_flags = RELSIM_ADJUST_OPENINGS;
	crs.openings = mpt->raid_queue_depth;
	xpt_action((union ccb *)&crs);
	if (crs.ccb_h.status != CAM_REQ_CMP)
		mpt_vol_prt(mpt, mpt_vol, "mpt_adjust_queue_depth failed "
			    "with CAM status %#x\n", crs.ccb_h.status);
}

static void
mpt_announce_vol(struct mpt_softc *mpt, struct mpt_raid_volume *mpt_vol)
{
	CONFIG_PAGE_RAID_VOL_0 *vol_pg;
	u_int i;

	vol_pg = mpt_vol->config_page;
	mpt_vol_prt(mpt, mpt_vol, "Settings (");
	for (i = 1; i <= 0x8000; i <<= 1) {
		switch (vol_pg->VolumeSettings.Settings & i) {
		case MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE:
			mpt_prtc(mpt, " Member-WCE");
			break;
		case MPI_RAIDVOL0_SETTING_OFFLINE_ON_SMART:
			mpt_prtc(mpt, " Offline-On-SMART-Err");
			break;
		case MPI_RAIDVOL0_SETTING_AUTO_CONFIGURE:
			mpt_prtc(mpt, " Hot-Plug-Spares");
			break;
		case MPI_RAIDVOL0_SETTING_PRIORITY_RESYNC:
			mpt_prtc(mpt, " High-Priority-ReSync");
			break;
		default:
			break;
		}
	}
	mpt_prtc(mpt, " )\n");
	if (vol_pg->VolumeSettings.HotSparePool != 0) {
		mpt_vol_prt(mpt, mpt_vol, "Using Spare Pool%s",
			    powerof2(vol_pg->VolumeSettings.HotSparePool)
			  ? ":" : "s:");
		for (i = 0; i < 8; i++) {
			u_int mask;

			mask = 0x1 << i;
			if ((vol_pg->VolumeSettings.HotSparePool & mask) == 0)
				continue;
			mpt_prtc(mpt, " %d", i);
		}
		mpt_prtc(mpt, "\n");
	}
	mpt_vol_prt(mpt, mpt_vol, "%d Members:\n", vol_pg->NumPhysDisks);
	for (i = 0; i < vol_pg->NumPhysDisks; i++){
		struct mpt_raid_disk *mpt_disk;
		CONFIG_PAGE_RAID_PHYS_DISK_0 *disk_pg;
		int pt_bus = cam_sim_bus(mpt->phydisk_sim);
		U8 f, s;

		mpt_disk = mpt->raid_disks + vol_pg->PhysDisk[i].PhysDiskNum;
		disk_pg = &mpt_disk->config_page;
		mpt_prtc(mpt, "      ");
		mpt_prtc(mpt, "(%s:%d:%d:0): ", device_get_nameunit(mpt->dev),
			 pt_bus, disk_pg->PhysDiskID);
		if (vol_pg->VolumeType == MPI_RAID_VOL_TYPE_IM) {
			mpt_prtc(mpt, "%s", mpt_disk->member_number == 0?
			    "Primary" : "Secondary");
		} else {
			mpt_prtc(mpt, "Stripe Position %d",
				 mpt_disk->member_number);
		}
		f = disk_pg->PhysDiskStatus.Flags;
		s = disk_pg->PhysDiskStatus.State;
		if (f & MPI_PHYSDISK0_STATUS_FLAG_OUT_OF_SYNC) {
			mpt_prtc(mpt, " Out of Sync");
		}
		if (f & MPI_PHYSDISK0_STATUS_FLAG_QUIESCED) {
			mpt_prtc(mpt, " Quiesced");
		}
		if (f & MPI_PHYSDISK0_STATUS_FLAG_INACTIVE_VOLUME) {
			mpt_prtc(mpt, " Inactive");
		}
		if (f & MPI_PHYSDISK0_STATUS_FLAG_OPTIMAL_PREVIOUS) {
			mpt_prtc(mpt, " Was Optimal");
		}
		if (f & MPI_PHYSDISK0_STATUS_FLAG_NOT_OPTIMAL_PREVIOUS) {
			mpt_prtc(mpt, " Was Non-Optimal");
		}
		switch (s) {
		case MPI_PHYSDISK0_STATUS_ONLINE:
			mpt_prtc(mpt, " Online");
			break;
		case MPI_PHYSDISK0_STATUS_MISSING:
			mpt_prtc(mpt, " Missing");
			break;
		case MPI_PHYSDISK0_STATUS_NOT_COMPATIBLE:
			mpt_prtc(mpt, " Incompatible");
			break;
		case MPI_PHYSDISK0_STATUS_FAILED:
			mpt_prtc(mpt, " Failed");
			break;
		case MPI_PHYSDISK0_STATUS_INITIALIZING:
			mpt_prtc(mpt, " Initializing");
			break;
		case MPI_PHYSDISK0_STATUS_OFFLINE_REQUESTED:
			mpt_prtc(mpt, " Requested Offline");
			break;
		case MPI_PHYSDISK0_STATUS_FAILED_REQUESTED:
			mpt_prtc(mpt, " Requested Failed");
			break;
		case MPI_PHYSDISK0_STATUS_OTHER_OFFLINE:
		default:
			mpt_prtc(mpt, " Offline Other (%x)", s);
			break;
		}
		mpt_prtc(mpt, "\n");
	}
}

static void
mpt_announce_disk(struct mpt_softc *mpt, struct mpt_raid_disk *mpt_disk)
{
	CONFIG_PAGE_RAID_PHYS_DISK_0 *disk_pg;
	int rd_bus = cam_sim_bus(mpt->sim);
	int pt_bus = cam_sim_bus(mpt->phydisk_sim);
	u_int i;

	disk_pg = &mpt_disk->config_page;
	mpt_disk_prt(mpt, mpt_disk,
		     "Physical (%s:%d:%d:0), Pass-thru (%s:%d:%d:0)\n",
		     device_get_nameunit(mpt->dev), rd_bus,
		     disk_pg->PhysDiskID, device_get_nameunit(mpt->dev),
		     pt_bus, mpt_disk - mpt->raid_disks);
	if (disk_pg->PhysDiskSettings.HotSparePool == 0)
		return;
	mpt_disk_prt(mpt, mpt_disk, "Member of Hot Spare Pool%s",
		     powerof2(disk_pg->PhysDiskSettings.HotSparePool)
		   ? ":" : "s:");
	for (i = 0; i < 8; i++) {
		u_int mask;

		mask = 0x1 << i;
		if ((disk_pg->PhysDiskSettings.HotSparePool & mask) == 0)
			continue;
		mpt_prtc(mpt, " %d", i);
	}
	mpt_prtc(mpt, "\n");
}

static void
mpt_refresh_raid_disk(struct mpt_softc *mpt, struct mpt_raid_disk *mpt_disk,
		      IOC_3_PHYS_DISK *ioc_disk)
{
	int rv;

	rv = mpt_read_cfg_header(mpt, MPI_CONFIG_PAGETYPE_RAID_PHYSDISK,
				 /*PageNumber*/0, ioc_disk->PhysDiskNum,
				 &mpt_disk->config_page.Header,
				 /*sleep_ok*/TRUE, /*timeout_ms*/5000);
	if (rv != 0) {
		mpt_prt(mpt, "mpt_refresh_raid_disk: "
			"Failed to read RAID Disk Hdr(%d)\n",
		 	ioc_disk->PhysDiskNum);
		return;
	}
	rv = mpt_read_cur_cfg_page(mpt, ioc_disk->PhysDiskNum,
				   &mpt_disk->config_page.Header,
				   sizeof(mpt_disk->config_page),
				   /*sleep_ok*/TRUE, /*timeout_ms*/5000);
	if (rv != 0)
		mpt_prt(mpt, "mpt_refresh_raid_disk: "
			"Failed to read RAID Disk Page(%d)\n",
		 	ioc_disk->PhysDiskNum);
	mpt2host_config_page_raid_phys_disk_0(&mpt_disk->config_page);
}

static void
mpt_refresh_raid_vol(struct mpt_softc *mpt, struct mpt_raid_volume *mpt_vol,
    CONFIG_PAGE_IOC_2_RAID_VOL *ioc_vol)
{
	CONFIG_PAGE_RAID_VOL_0 *vol_pg;
	struct mpt_raid_action_result *ar;
	request_t *req;
	int rv;
	int i;

	vol_pg = mpt_vol->config_page;
	mpt_vol->flags &= ~MPT_RVF_UP2DATE;

	rv = mpt_read_cfg_header(mpt, MPI_CONFIG_PAGETYPE_RAID_VOLUME, 0,
	    ioc_vol->VolumePageNumber, &vol_pg->Header, TRUE, 5000);
	if (rv != 0) {
		mpt_vol_prt(mpt, mpt_vol,
		    "mpt_refresh_raid_vol: Failed to read RAID Vol Hdr(%d)\n",
		    ioc_vol->VolumePageNumber);
		return;
	}

	rv = mpt_read_cur_cfg_page(mpt, ioc_vol->VolumePageNumber,
	    &vol_pg->Header, mpt->raid_page0_len, TRUE, 5000);
	if (rv != 0) {
		mpt_vol_prt(mpt, mpt_vol,
		    "mpt_refresh_raid_vol: Failed to read RAID Vol Page(%d)\n",
		    ioc_vol->VolumePageNumber);
		return;
	}
	mpt2host_config_page_raid_vol_0(vol_pg);

	mpt_vol->flags |= MPT_RVF_ACTIVE;

	/* Update disk entry array data. */
	for (i = 0; i < vol_pg->NumPhysDisks; i++) {
		struct mpt_raid_disk *mpt_disk;
		mpt_disk = mpt->raid_disks + vol_pg->PhysDisk[i].PhysDiskNum;
		mpt_disk->volume = mpt_vol;
		mpt_disk->member_number = vol_pg->PhysDisk[i].PhysDiskMap;
		if (vol_pg->VolumeType == MPI_RAID_VOL_TYPE_IM) {
			mpt_disk->member_number--;
		}
	}

	if ((vol_pg->VolumeStatus.Flags
	   & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS) == 0)
		return;

	req = mpt_get_request(mpt, TRUE);
	if (req == NULL) {
		mpt_vol_prt(mpt, mpt_vol,
		    "mpt_refresh_raid_vol: Get request failed!\n");
		return;
	}
	rv = mpt_issue_raid_req(mpt, mpt_vol, NULL, req,
	    MPI_RAID_ACTION_INDICATOR_STRUCT, 0, 0, 0, FALSE, TRUE);
	if (rv == ETIMEDOUT) {
		mpt_vol_prt(mpt, mpt_vol,
		    "mpt_refresh_raid_vol: Progress Indicator fetch timeout\n");
		mpt_free_request(mpt, req);
		return;
	}

	ar = REQ_TO_RAID_ACTION_RESULT(req);
	if (rv == 0
	 && ar->action_status == MPI_RAID_ACTION_ASTATUS_SUCCESS
	 && REQ_IOCSTATUS(req) == MPI_IOCSTATUS_SUCCESS) {
		memcpy(&mpt_vol->sync_progress,
		       &ar->action_data.indicator_struct,
		       sizeof(mpt_vol->sync_progress));
		mpt2host_mpi_raid_vol_indicator(&mpt_vol->sync_progress);
	} else {
		mpt_vol_prt(mpt, mpt_vol,
		    "mpt_refresh_raid_vol: Progress indicator fetch failed!\n");
	}
	mpt_free_request(mpt, req);
}

/*
 * Update in-core information about RAID support.  We update any entries
 * that didn't previously exists or have been marked as needing to
 * be updated by our event handler.  Interesting changes are displayed
 * to the console.
 */
static int
mpt_refresh_raid_data(struct mpt_softc *mpt)
{
	CONFIG_PAGE_IOC_2_RAID_VOL *ioc_vol;
	CONFIG_PAGE_IOC_2_RAID_VOL *ioc_last_vol;
	IOC_3_PHYS_DISK *ioc_disk;
	IOC_3_PHYS_DISK *ioc_last_disk;
	CONFIG_PAGE_RAID_VOL_0	*vol_pg;
	size_t len;
	int rv;
	int i;
	u_int nonopt_volumes;

	if (mpt->ioc_page2 == NULL || mpt->ioc_page3 == NULL) {
		return (0);
	}

	/*
	 * Mark all items as unreferenced by the configuration.
	 * This allows us to find, report, and discard stale
	 * entries.
	 */
	for (i = 0; i < mpt->ioc_page2->MaxPhysDisks; i++) {
		mpt->raid_disks[i].flags &= ~MPT_RDF_REFERENCED;
	}
	for (i = 0; i < mpt->ioc_page2->MaxVolumes; i++) {
		mpt->raid_volumes[i].flags &= ~MPT_RVF_REFERENCED;
	}

	/*
	 * Get Physical Disk information.
	 */
	len = mpt->ioc_page3->Header.PageLength * sizeof(uint32_t);
	rv = mpt_read_cur_cfg_page(mpt, /*PageAddress*/0,
				   &mpt->ioc_page3->Header, len,
				   /*sleep_ok*/TRUE, /*timeout_ms*/5000);
	if (rv) {
		mpt_prt(mpt,
		    "mpt_refresh_raid_data: Failed to read IOC Page 3\n");
		return (-1);
	}
	mpt2host_config_page_ioc3(mpt->ioc_page3);

	ioc_disk = mpt->ioc_page3->PhysDisk;
	ioc_last_disk = ioc_disk + mpt->ioc_page3->NumPhysDisks;
	for (; ioc_disk != ioc_last_disk; ioc_disk++) {
		struct mpt_raid_disk *mpt_disk;

		mpt_disk = mpt->raid_disks + ioc_disk->PhysDiskNum;
		mpt_disk->flags |= MPT_RDF_REFERENCED;
		if ((mpt_disk->flags & (MPT_RDF_ACTIVE|MPT_RDF_UP2DATE))
		 != (MPT_RDF_ACTIVE|MPT_RDF_UP2DATE)) {

			mpt_refresh_raid_disk(mpt, mpt_disk, ioc_disk);

		}
		mpt_disk->flags |= MPT_RDF_ACTIVE;
		mpt->raid_rescan++;
	}

	/*
	 * Refresh volume data.
	 */
	len = mpt->ioc_page2->Header.PageLength * sizeof(uint32_t);
	rv = mpt_read_cur_cfg_page(mpt, /*PageAddress*/0,
				   &mpt->ioc_page2->Header, len,
				   /*sleep_ok*/TRUE, /*timeout_ms*/5000);
	if (rv) {
		mpt_prt(mpt, "mpt_refresh_raid_data: "
			"Failed to read IOC Page 2\n");
		return (-1);
	}
	mpt2host_config_page_ioc2(mpt->ioc_page2);

	ioc_vol = mpt->ioc_page2->RaidVolume;
	ioc_last_vol = ioc_vol + mpt->ioc_page2->NumActiveVolumes;
	for (;ioc_vol != ioc_last_vol; ioc_vol++) {
		struct mpt_raid_volume *mpt_vol;

		mpt_vol = mpt->raid_volumes + ioc_vol->VolumePageNumber;
		mpt_vol->flags |= MPT_RVF_REFERENCED;
		vol_pg = mpt_vol->config_page;
		if (vol_pg == NULL)
			continue;
		if (((mpt_vol->flags & (MPT_RVF_ACTIVE|MPT_RVF_UP2DATE))
		  != (MPT_RVF_ACTIVE|MPT_RVF_UP2DATE))
		 || (vol_pg->VolumeStatus.Flags
		   & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS) != 0) {

			mpt_refresh_raid_vol(mpt, mpt_vol, ioc_vol);
		}
		mpt_vol->flags |= MPT_RVF_ACTIVE;
	}

	nonopt_volumes = 0;
	for (i = 0; i < mpt->ioc_page2->MaxVolumes; i++) {
		struct mpt_raid_volume *mpt_vol;
		uint64_t total;
		uint64_t left;
		int m;
		u_int prio;

		mpt_vol = &mpt->raid_volumes[i];

		if ((mpt_vol->flags & MPT_RVF_ACTIVE) == 0) {
			continue;
		}

		vol_pg = mpt_vol->config_page;
		if ((mpt_vol->flags & (MPT_RVF_REFERENCED|MPT_RVF_ANNOUNCED))
		 == MPT_RVF_ANNOUNCED) {
			mpt_vol_prt(mpt, mpt_vol, "No longer configured\n");
			mpt_vol->flags = 0;
			continue;
		}

		if ((mpt_vol->flags & MPT_RVF_ANNOUNCED) == 0) {
			mpt_announce_vol(mpt, mpt_vol);
			mpt_vol->flags |= MPT_RVF_ANNOUNCED;
		}

		if (vol_pg->VolumeStatus.State !=
		    MPI_RAIDVOL0_STATUS_STATE_OPTIMAL)
			nonopt_volumes++;

		if ((mpt_vol->flags & MPT_RVF_UP2DATE) != 0)
			continue;

		mpt_vol->flags |= MPT_RVF_UP2DATE;
		mpt_vol_prt(mpt, mpt_vol, "%s - %s\n",
		    mpt_vol_type(mpt_vol), mpt_vol_state(mpt_vol));
		mpt_verify_mwce(mpt, mpt_vol);

		if (vol_pg->VolumeStatus.Flags == 0) {
			continue;
		}

		mpt_vol_prt(mpt, mpt_vol, "Status (");
		for (m = 1; m <= 0x80; m <<= 1) {
			switch (vol_pg->VolumeStatus.Flags & m) {
			case MPI_RAIDVOL0_STATUS_FLAG_ENABLED:
				mpt_prtc(mpt, " Enabled");
				break;
			case MPI_RAIDVOL0_STATUS_FLAG_QUIESCED:
				mpt_prtc(mpt, " Quiesced");
				break;
			case MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS:
				mpt_prtc(mpt, " Re-Syncing");
				break;
			case MPI_RAIDVOL0_STATUS_FLAG_VOLUME_INACTIVE:
				mpt_prtc(mpt, " Inactive");
				break;
			default:
				break;
			}
		}
		mpt_prtc(mpt, " )\n");

		if ((vol_pg->VolumeStatus.Flags
		   & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS) == 0)
			continue;

		mpt_verify_resync_rate(mpt, mpt_vol);

		left = MPT_U64_2_SCALAR(mpt_vol->sync_progress.BlocksRemaining);
		total = MPT_U64_2_SCALAR(mpt_vol->sync_progress.TotalBlocks);
		if (vol_pg->ResyncRate != 0) {

			prio = ((u_int)vol_pg->ResyncRate * 100000) / 0xFF;
			mpt_vol_prt(mpt, mpt_vol, "Rate %d.%d%%\n",
			    prio / 1000, prio % 1000);
		} else {
			prio = vol_pg->VolumeSettings.Settings
			     & MPI_RAIDVOL0_SETTING_PRIORITY_RESYNC;
			mpt_vol_prt(mpt, mpt_vol, "%s Priority Re-Sync\n",
			    prio ? "High" : "Low");
		}
		mpt_vol_prt(mpt, mpt_vol, "%ju of %ju "
			    "blocks remaining\n", (uintmax_t)left,
			    (uintmax_t)total);

		/* Periodically report on sync progress. */
		mpt_schedule_raid_refresh(mpt);
	}

	for (i = 0; i < mpt->ioc_page2->MaxPhysDisks; i++) {
		struct mpt_raid_disk *mpt_disk;
		CONFIG_PAGE_RAID_PHYS_DISK_0 *disk_pg;
		int m;

		mpt_disk = &mpt->raid_disks[i];
		disk_pg = &mpt_disk->config_page;

		if ((mpt_disk->flags & MPT_RDF_ACTIVE) == 0)
			continue;

		if ((mpt_disk->flags & (MPT_RDF_REFERENCED|MPT_RDF_ANNOUNCED))
		 == MPT_RDF_ANNOUNCED) {
			mpt_disk_prt(mpt, mpt_disk, "No longer configured\n");
			mpt_disk->flags = 0;
			mpt->raid_rescan++;
			continue;
		}

		if ((mpt_disk->flags & MPT_RDF_ANNOUNCED) == 0) {

			mpt_announce_disk(mpt, mpt_disk);
			mpt_disk->flags |= MPT_RVF_ANNOUNCED;
		}

		if ((mpt_disk->flags & MPT_RDF_UP2DATE) != 0)
			continue;

		mpt_disk->flags |= MPT_RDF_UP2DATE;
		mpt_disk_prt(mpt, mpt_disk, "%s\n", mpt_disk_state(mpt_disk));
		if (disk_pg->PhysDiskStatus.Flags == 0)
			continue;

		mpt_disk_prt(mpt, mpt_disk, "Status (");
		for (m = 1; m <= 0x80; m <<= 1) {
			switch (disk_pg->PhysDiskStatus.Flags & m) {
			case MPI_PHYSDISK0_STATUS_FLAG_OUT_OF_SYNC:
				mpt_prtc(mpt, " Out-Of-Sync");
				break;
			case MPI_PHYSDISK0_STATUS_FLAG_QUIESCED:
				mpt_prtc(mpt, " Quiesced");
				break;
			default:
				break;
			}
		}
		mpt_prtc(mpt, " )\n");
	}

	mpt->raid_nonopt_volumes = nonopt_volumes;
	return (0);
}

static void
mpt_raid_timer(void *arg)
{
	struct mpt_softc *mpt;

	mpt = (struct mpt_softc *)arg;
	MPT_LOCK_ASSERT(mpt);
	mpt_raid_wakeup(mpt);
}

static void
mpt_schedule_raid_refresh(struct mpt_softc *mpt)
{

	callout_reset(&mpt->raid_timer, MPT_RAID_SYNC_REPORT_INTERVAL,
		      mpt_raid_timer, mpt);
}

void
mpt_raid_free_mem(struct mpt_softc *mpt)
{

	if (mpt->raid_volumes) {
		struct mpt_raid_volume *mpt_raid;
		int i;
		for (i = 0; i < mpt->raid_max_volumes; i++) {
			mpt_raid = &mpt->raid_volumes[i];
			if (mpt_raid->config_page) {
				free(mpt_raid->config_page, M_DEVBUF);
				mpt_raid->config_page = NULL;
			}
		}
		free(mpt->raid_volumes, M_DEVBUF);
		mpt->raid_volumes = NULL;
	}
	if (mpt->raid_disks) {
		free(mpt->raid_disks, M_DEVBUF);
		mpt->raid_disks = NULL;
	}
	if (mpt->ioc_page2) {
		free(mpt->ioc_page2, M_DEVBUF);
		mpt->ioc_page2 = NULL;
	}
	if (mpt->ioc_page3) {
		free(mpt->ioc_page3, M_DEVBUF);
		mpt->ioc_page3 = NULL;
	}
	mpt->raid_max_volumes =  0;
	mpt->raid_max_disks =  0;
}

static int
mpt_raid_set_vol_resync_rate(struct mpt_softc *mpt, u_int rate)
{
	struct mpt_raid_volume *mpt_vol;

	if ((rate > MPT_RAID_RESYNC_RATE_MAX
	  || rate < MPT_RAID_RESYNC_RATE_MIN)
	 && rate != MPT_RAID_RESYNC_RATE_NC)
		return (EINVAL);

	MPT_LOCK(mpt);
	mpt->raid_resync_rate = rate;
	RAID_VOL_FOREACH(mpt, mpt_vol) {
		if ((mpt_vol->flags & MPT_RVF_ACTIVE) == 0) {
			continue;
		}
		mpt_verify_resync_rate(mpt, mpt_vol);
	}
	MPT_UNLOCK(mpt);
	return (0);
}

static int
mpt_raid_set_vol_queue_depth(struct mpt_softc *mpt, u_int vol_queue_depth)
{
	struct mpt_raid_volume *mpt_vol;

	if (vol_queue_depth > 255 || vol_queue_depth < 1)
		return (EINVAL);

	MPT_LOCK(mpt);
	mpt->raid_queue_depth = vol_queue_depth;
	RAID_VOL_FOREACH(mpt, mpt_vol) {
		struct cam_path *path;
		int error;

		if ((mpt_vol->flags & MPT_RVF_ACTIVE) == 0)
			continue;

		mpt->raid_rescan = 0;

		error = xpt_create_path(&path, NULL,
					cam_sim_path(mpt->sim),
					mpt_vol->config_page->VolumeID,
					/*lun*/0);
		if (error != CAM_REQ_CMP) {
			mpt_vol_prt(mpt, mpt_vol, "Unable to allocate path!\n");
			continue;
		}
		mpt_adjust_queue_depth(mpt, mpt_vol, path);
		xpt_free_path(path);
	}
	MPT_UNLOCK(mpt);
	return (0);
}

static int
mpt_raid_set_vol_mwce(struct mpt_softc *mpt, mpt_raid_mwce_t mwce)
{
	struct mpt_raid_volume *mpt_vol;
	int force_full_resync;

	MPT_LOCK(mpt);
	if (mwce == mpt->raid_mwce_setting) {
		MPT_UNLOCK(mpt);
		return (0);
	}

	/*
	 * Catch MWCE being left on due to a failed shutdown.  Since
	 * sysctls cannot be set by the loader, we treat the first
	 * setting of this varible specially and force a full volume
	 * resync if MWCE is enabled and a resync is in progress.
	 */
	force_full_resync = 0;
	if (mpt->raid_mwce_set == 0
	 && mpt->raid_mwce_setting == MPT_RAID_MWCE_NC
	 && mwce == MPT_RAID_MWCE_REBUILD_ONLY)
		force_full_resync = 1;

	mpt->raid_mwce_setting = mwce;
	RAID_VOL_FOREACH(mpt, mpt_vol) {
		CONFIG_PAGE_RAID_VOL_0 *vol_pg;
		int resyncing;
		int mwce;

		if ((mpt_vol->flags & MPT_RVF_ACTIVE) == 0)
			continue;

		vol_pg = mpt_vol->config_page;
		resyncing = vol_pg->VolumeStatus.Flags
			  & MPI_RAIDVOL0_STATUS_FLAG_RESYNC_IN_PROGRESS;
		mwce = vol_pg->VolumeSettings.Settings
		     & MPI_RAIDVOL0_SETTING_WRITE_CACHING_ENABLE;
		if (force_full_resync && resyncing && mwce) {

			/*
			 * XXX disable/enable volume should force a resync,
			 *     but we'll need to queice, drain, and restart
			 *     I/O to do that.
			 */
			mpt_vol_prt(mpt, mpt_vol, "WARNING - Unsafe shutdown "
				    "detected.  Suggest full resync.\n");
		}
		mpt_verify_mwce(mpt, mpt_vol);
	}
	mpt->raid_mwce_set = 1;
	MPT_UNLOCK(mpt);
	return (0);
}

static const char *mpt_vol_mwce_strs[] =
{
	"On",
	"Off",
	"On-During-Rebuild",
	"NC"
};

static int
mpt_raid_sysctl_vol_member_wce(SYSCTL_HANDLER_ARGS)
{
	char inbuf[20];
	struct mpt_softc *mpt;
	const char *str;
	int error;
	u_int size;
	u_int i;

	GIANT_REQUIRED;

	mpt = (struct mpt_softc *)arg1;
	str = mpt_vol_mwce_strs[mpt->raid_mwce_setting];
	error = SYSCTL_OUT(req, str, strlen(str) + 1);
	if (error || !req->newptr) {
		return (error);
	}

	size = req->newlen - req->newidx;
	if (size >= sizeof(inbuf)) {
		return (EINVAL);
	}

	error = SYSCTL_IN(req, inbuf, size);
	if (error) {
		return (error);
	}
	inbuf[size] = '\0'; 
	for (i = 0; i < NUM_ELEMENTS(mpt_vol_mwce_strs); i++) {
		if (strcmp(mpt_vol_mwce_strs[i], inbuf) == 0) {
			return (mpt_raid_set_vol_mwce(mpt, i));
		}
	}
	return (EINVAL);
}

static int
mpt_raid_sysctl_vol_resync_rate(SYSCTL_HANDLER_ARGS)
{
	struct mpt_softc *mpt;
	u_int raid_resync_rate;
	int error;

	GIANT_REQUIRED;

	mpt = (struct mpt_softc *)arg1;
	raid_resync_rate = mpt->raid_resync_rate;

	error = sysctl_handle_int(oidp, &raid_resync_rate, 0, req);
	if (error || !req->newptr) {
		return error;
	}

	return (mpt_raid_set_vol_resync_rate(mpt, raid_resync_rate));
}

static int
mpt_raid_sysctl_vol_queue_depth(SYSCTL_HANDLER_ARGS)
{
	struct mpt_softc *mpt;
	u_int raid_queue_depth;
	int error;

	GIANT_REQUIRED;

	mpt = (struct mpt_softc *)arg1;
	raid_queue_depth = mpt->raid_queue_depth;

	error = sysctl_handle_int(oidp, &raid_queue_depth, 0, req);
	if (error || !req->newptr) {
		return error;
	}

	return (mpt_raid_set_vol_queue_depth(mpt, raid_queue_depth));
}

static void
mpt_raid_sysctl_attach(struct mpt_softc *mpt)
{
	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(mpt->dev);
	struct sysctl_oid *tree = device_get_sysctl_tree(mpt->dev);

	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
			"vol_member_wce", CTLTYPE_STRING | CTLFLAG_RW, mpt, 0,
			mpt_raid_sysctl_vol_member_wce, "A",
			"volume member WCE(On,Off,On-During-Rebuild,NC)");

	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
			"vol_queue_depth", CTLTYPE_INT | CTLFLAG_RW, mpt, 0,
			mpt_raid_sysctl_vol_queue_depth, "I",
			"default volume queue depth");

	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
			"vol_resync_rate", CTLTYPE_INT | CTLFLAG_RW, mpt, 0,
			mpt_raid_sysctl_vol_resync_rate, "I",
			"volume resync priority (0 == NC, 1 - 255)");
	SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
			"nonoptimal_volumes", CTLFLAG_RD,
			&mpt->raid_nonopt_volumes, 0,
			"number of nonoptimal volumes");
}
