/*
 * Copyright (c) 2004-07 Applied Micro Circuits Corporation.
 * Copyright (c) 2004-05 Vinod Kashyap
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	$FreeBSD$
 */

/*
 * AMCC'S 3ware driver for 9000 series storage controllers.
 *
 * Author: Vinod Kashyap
 * Modifications by: Adam Radford
 * Modifications by: Manjunath Ranganathaiah
 */


/*
 * Common Layer miscellaneous functions.
 */


#include "tw_osl_share.h"
#include "tw_cl_share.h"
#include "tw_cl_fwif.h"
#include "tw_cl_ioctl.h"
#include "tw_cl.h"
#include "tw_cl_externs.h"
#include "tw_osl_ioctl.h"



/* AEN severity table. */
TW_INT8	*tw_cli_severity_string_table[] = {
	"None",
	TW_CL_SEVERITY_ERROR_STRING,
	TW_CL_SEVERITY_WARNING_STRING,
	TW_CL_SEVERITY_INFO_STRING,
	TW_CL_SEVERITY_DEBUG_STRING,
	""
};



/*
 * Function name:	tw_cli_drain_complete_queue
 * Description:		This function gets called during a controller reset.
 *			It errors back to the OS Layer, all those requests that
 *			are in the complete queue, at the time of the reset.
 *			Any CL internal requests will be simply freed.
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cli_drain_complete_queue(struct tw_cli_ctlr_context *ctlr)
{
	struct tw_cli_req_context	*req;
	struct tw_cl_req_packet		*req_pkt;

	tw_cli_dbg_printf(3, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	/* Walk the busy queue. */
	while ((req = tw_cli_req_q_remove_head(ctlr, TW_CLI_COMPLETE_Q)) !=
		TW_CL_NULL) {
		if (req->flags & TW_CLI_REQ_FLAGS_INTERNAL) {
			/*
			 * It's an internal request.  Set the appropriate
			 * error and call the CL internal callback if there's
			 * one.  If the request originator is polling for
			 * completion, he should be checking req->error to
			 * determine that the request did not go through.
			 * The request originators are responsible for the
			 * clean-up.
			 */
			req->error_code = TW_CL_ERR_REQ_BUS_RESET;
			if (req->tw_cli_callback)
				req->tw_cli_callback(req);
		} else if (req->flags & TW_CLI_REQ_FLAGS_PASSTHRU) {
			/* It's a passthru request.  Complete it. */
			if ((req_pkt = req->orig_req) != TW_CL_NULL) {
				req_pkt->status = TW_CL_ERR_REQ_BUS_RESET;

				if (req_pkt->tw_osl_callback)
					req_pkt->tw_osl_callback(req->req_handle);
			}
			tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
		} else {
			/* It's an external (SCSI) request.  Add it to the reset queue. */
			tw_osl_untimeout(req->req_handle);
			tw_cli_req_q_insert_tail(req, TW_CLI_RESET_Q);
		}
	} /* End of while loop */
}



/*
 * Function name:	tw_cli_drain_busy_queue
 * Description:		This function gets called during a controller reset.
 *			It errors back to the OS Layer, all those requests that
 *			were pending with the firmware, at the time of the
 *			reset.
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cli_drain_busy_queue(struct tw_cli_ctlr_context *ctlr)
{
	struct tw_cli_req_context	*req;
	struct tw_cl_req_packet		*req_pkt;

	tw_cli_dbg_printf(3, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	/* Walk the busy queue. */
	while ((req = tw_cli_req_q_remove_head(ctlr, TW_CLI_BUSY_Q)) !=
		TW_CL_NULL) {
		if (req->flags & TW_CLI_REQ_FLAGS_INTERNAL) {
			/*
			 * It's an internal request.  Set the appropriate
			 * error and call the CL internal callback if there's
			 * one.  If the request originator is polling for
			 * completion, he should be checking req->error to
			 * determine that the request did not go through.
			 * The request originators are responsible for the
			 * clean-up.
			 */
			req->error_code = TW_CL_ERR_REQ_BUS_RESET;
			if (req->tw_cli_callback)
				req->tw_cli_callback(req);
		} else if (req->flags & TW_CLI_REQ_FLAGS_PASSTHRU) {
			/* It's a passthru request.  Complete it. */
			if ((req_pkt = req->orig_req) != TW_CL_NULL) {
				req_pkt->status = TW_CL_ERR_REQ_BUS_RESET;

				if (req_pkt->tw_osl_callback)
					req_pkt->tw_osl_callback(req->req_handle);
			}
			tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
		} else {
			/* It's an external (SCSI) request.  Add it to the reset queue. */
			tw_osl_untimeout(req->req_handle);
			tw_cli_req_q_insert_tail(req, TW_CLI_RESET_Q);
		}
	} /* End of while loop */
}



/*
 * Function name:	tw_cli_drain_pending_queue
 * Description:		This function gets called during a controller reset.
 *			It errors back to the OS Layer, all those requests that
 *			were in the pending queue, at the time of the reset.
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 * Output:		None
 * Return value:	None
 */

TW_VOID
tw_cli_drain_pending_queue(struct tw_cli_ctlr_context *ctlr)
{
	struct tw_cli_req_context	*req;
	struct tw_cl_req_packet		*req_pkt;
    
	tw_cli_dbg_printf(3, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
	
	/*
	 * Pull requests off the pending queue, and complete them.
	 */
	while ((req = tw_cli_req_q_remove_head(ctlr, TW_CLI_PENDING_Q)) !=
		TW_CL_NULL) {
		if (req->flags & TW_CLI_REQ_FLAGS_INTERNAL) {
			/*
			 * It's an internal request.  Set the appropriate
			 * error and call the CL internal callback if there's
			 * one.  If the request originator is polling for
			 * completion, he should be checking req->error to
			 * determine that the request did not go through.
			 * The request originators are responsible for the
			 * clean-up.
			 */
			req->error_code = TW_CL_ERR_REQ_BUS_RESET;
			if (req->tw_cli_callback)
				req->tw_cli_callback(req);
		} else if (req->flags & TW_CLI_REQ_FLAGS_PASSTHRU) {
			/* It's a passthru request.  Complete it. */
			if ((req_pkt = req->orig_req) != TW_CL_NULL) {
				req_pkt->status = TW_CL_ERR_REQ_BUS_RESET;

				if (req_pkt->tw_osl_callback)
					req_pkt->tw_osl_callback(req->req_handle);
			}
			tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
		} else {
			/* It's an external (SCSI) request.  Add it to the reset queue. */
			tw_osl_untimeout(req->req_handle);
			tw_cli_req_q_insert_tail(req, TW_CLI_RESET_Q);
		}
	} /* End of while loop */
}



/*
 * Function name:	tw_cli_drain_response_queue
 * Description:		Drain the controller response queue.
 *
 * Input:		ctlr	-- ptr to per ctlr structure
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_drain_response_queue(struct tw_cli_ctlr_context *ctlr)
{
	TW_UINT32	resp;
	TW_UINT32	status_reg;

	tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	for (;;) {
		status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr->ctlr_handle);

		if (status_reg & TWA_STATUS_RESPONSE_QUEUE_EMPTY)
			return(TW_OSL_ESUCCESS); /* no more response queue entries */

		resp = TW_CLI_READ_RESPONSE_QUEUE(ctlr->ctlr_handle);
	}
}



/*
 * Function name:	tw_cli_find_response
 * Description:		Find a particular response in the ctlr response queue.
 *
 * Input:		ctlr	-- ptr to per ctlr structure
 *			req_id	-- request id of the response to look for
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_find_response(struct tw_cli_ctlr_context *ctlr, TW_INT32 req_id)
{
	TW_UINT32	resp;
	TW_INT32	resp_id;
	TW_UINT32	status_reg;

	tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	for (;;) {
		status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr->ctlr_handle);

		if (status_reg & TWA_STATUS_RESPONSE_QUEUE_EMPTY)
			return(TW_OSL_ENOTTY); /* no more response queue entries */

		if (ctlr->device_id == TW_CL_DEVICE_ID_9K) {
			resp = TW_CLI_READ_RESPONSE_QUEUE(ctlr->ctlr_handle);
			resp_id = GET_RESP_ID(resp);
		} else {
			resp = TW_CLI_READ_LARGE_RESPONSE_QUEUE(
				ctlr->ctlr_handle);
			resp_id = GET_LARGE_RESP_ID(resp);
		}
		if (resp_id == req_id)
			return(TW_OSL_ESUCCESS); /* found the req_id */
	}
}



/*
 * Function name:	tw_cli_drain_aen_queue
 * Description:		Fetches all un-retrieved AEN's posted by fw.
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_drain_aen_queue(struct tw_cli_ctlr_context *ctlr)
{
	struct tw_cli_req_context	*req;
	struct tw_cl_command_header	*cmd_hdr;
	TW_TIME				end_time;
	TW_UINT16			aen_code;
	TW_INT32			error;

	tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	for (;;) {
		if ((req = tw_cli_get_request(ctlr
			)) == TW_CL_NULL) {
			error = TW_OSL_EBUSY;
			break;
		}

		req->flags |= TW_CLI_REQ_FLAGS_INTERNAL;
		req->tw_cli_callback = TW_CL_NULL;
		if ((error = tw_cli_send_scsi_cmd(req,
				0x03 /* REQUEST_SENSE */))) {
			tw_cli_dbg_printf(1, ctlr->ctlr_handle,
				tw_osl_cur_func(),
				"Cannot send command to fetch aen");
			break;
		}

		end_time = tw_osl_get_local_time() +
			TW_CLI_REQUEST_TIMEOUT_PERIOD;
		do {
			if ((error = req->error_code))
				/*
				 * This will take care of completion due to
				 * a reset, or a failure in
				 * tw_cli_submit_pending_queue.
				 */
				goto out;

			tw_cli_process_resp_intr(req->ctlr);

			if ((req->state != TW_CLI_REQ_STATE_BUSY) &&
				(req->state != TW_CLI_REQ_STATE_PENDING))
				break;
		} while (tw_osl_get_local_time() <= end_time);

		if (req->state != TW_CLI_REQ_STATE_COMPLETE) {
			error = TW_OSL_ETIMEDOUT;
			break;
		}

		if ((error = req->cmd_pkt->command.cmd_pkt_9k.status)) {
			cmd_hdr = &req->cmd_pkt->cmd_hdr;
#if       0
			tw_cli_create_ctlr_event(ctlr,
				TW_CL_MESSAGE_SOURCE_CONTROLLER_ERROR,
				cmd_hdr);
#endif // 0
			break;
		}

		aen_code = tw_cli_manage_aen(ctlr, req);
		if (aen_code == TWA_AEN_QUEUE_EMPTY)
			break;
		if (aen_code == TWA_AEN_SYNC_TIME_WITH_HOST)
			continue;

		ctlr->internal_req_busy = TW_CL_FALSE;
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	}

out:
	if (req) {
		if (req->data)
			ctlr->internal_req_busy = TW_CL_FALSE;
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	}
	return(error);
}



/*
 * Function name:	tw_cli_find_aen
 * Description:		Reports whether a given AEN ever occurred.
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 *			aen_code-- AEN to look for
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_find_aen(struct tw_cli_ctlr_context *ctlr, TW_UINT16 aen_code)
{
	TW_UINT32	last_index;
	TW_INT32	i;

	tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	if (ctlr->aen_q_wrapped)
		last_index = ctlr->aen_head;
	else
		last_index = 0;

	i = ctlr->aen_head;
	do {
		i = (i + ctlr->max_aens_supported - 1) %
			ctlr->max_aens_supported;
		if (ctlr->aen_queue[i].aen_code == aen_code)
			return(TW_OSL_ESUCCESS);
	} while (i != last_index);

	return(TW_OSL_EGENFAILURE);
}



/*
 * Function name:	tw_cli_poll_status
 * Description:		Poll for a given status to show up in the firmware
 *			status register.
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 *			status	-- status to look for
 *			timeout -- max # of seconds to wait before giving up
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_poll_status(struct tw_cli_ctlr_context *ctlr, TW_UINT32 status,
	TW_UINT32 timeout)
{
	TW_TIME		end_time;
	TW_UINT32	status_reg;

	tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	end_time = tw_osl_get_local_time() + timeout;
	do {
		status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr->ctlr_handle);
		if ((status_reg & status) == status)
			/* got the required bit(s) */
			return(TW_OSL_ESUCCESS);

		tw_osl_delay(1000);
	} while (tw_osl_get_local_time() <= end_time);

	return(TW_OSL_ETIMEDOUT);
}



/*
 * Function name:	tw_cl_create_event
 * Description:		Creates and queues ctlr/CL/OSL AEN's to be
 *			supplied to user-space tools on request.
 *			Also notifies OS Layer.
 * Input:		ctlr	-- ptr to CL internal ctlr context
 *			queue_event-- TW_CL_TRUE --> queue event;
 *				      TW_CL_FALSE--> don't queue event
 *							(simply notify OSL)
 *			event_src  -- source of event
 *			event_code -- AEN/error code
 *			severity -- severity of event
 *			severity_str--Text description of severity
 *			event_desc -- standard string related to the event/error
 *			event_specific_desc -- format string for additional
 *						info about the event
 *			... -- additional arguments conforming to the format
 *				specified by event_specific_desc
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cl_create_event(struct tw_cl_ctlr_handle *ctlr_handle,
	TW_UINT8 queue_event, TW_UINT8 event_src, TW_UINT16 event_code,
	TW_UINT8 severity, TW_UINT8 *severity_str, TW_UINT8 *event_desc,
	TW_UINT8 *event_specific_desc, ...)
{
	struct tw_cli_ctlr_context	*ctlr = ctlr_handle->cl_ctlr_ctxt;
	struct tw_cl_event_packet	event_pkt;
	struct tw_cl_event_packet	*event;
	TW_UINT32			aen_head;
	va_list				ap;

	tw_cli_dbg_printf(8, ctlr_handle, tw_osl_cur_func(), "entered");

	if ((ctlr) && (queue_event)) {
		/* Protect access to ctlr->aen_head. */
		tw_osl_get_lock(ctlr_handle, ctlr->gen_lock);

		aen_head = ctlr->aen_head;
		ctlr->aen_head = (aen_head + 1) % ctlr->max_aens_supported;

		/* Queue the event. */
		event = &(ctlr->aen_queue[aen_head]);
		tw_osl_memzero(event->parameter_data,
			sizeof(event->parameter_data));

		if (event->retrieved == TW_CL_AEN_NOT_RETRIEVED)
			ctlr->aen_q_overflow = TW_CL_TRUE;
		event->sequence_id = ++(ctlr->aen_cur_seq_id);
		if ((aen_head + 1) == ctlr->max_aens_supported) {
			tw_cli_dbg_printf(4, ctlr->ctlr_handle,
				tw_osl_cur_func(), "AEN queue wrapped");
			ctlr->aen_q_wrapped = TW_CL_TRUE;
		}

		/* Free access to ctlr->aen_head. */
		tw_osl_free_lock(ctlr_handle, ctlr->gen_lock);
	} else {
		event = &event_pkt;
		tw_osl_memzero(event, sizeof(struct tw_cl_event_packet));
	}

	event->event_src = event_src;
	event->time_stamp_sec = (TW_UINT32)tw_osl_get_local_time();
	event->aen_code = event_code;
	event->severity = severity;
	tw_osl_strcpy(event->severity_str, severity_str);
	event->retrieved = TW_CL_AEN_NOT_RETRIEVED;

	va_start(ap, event_specific_desc);
	tw_osl_vsprintf(event->parameter_data, event_specific_desc, ap);
	va_end(ap);

	event->parameter_len =
		(TW_UINT8)(tw_osl_strlen(event->parameter_data));
	tw_osl_strcpy(event->parameter_data + event->parameter_len + 1,
		event_desc);
	event->parameter_len += (1 + tw_osl_strlen(event_desc));

	tw_cli_dbg_printf(4, ctlr_handle, tw_osl_cur_func(),
		"event = %x %x %x %x %x %x %x\n %s",
		event->sequence_id,
		event->time_stamp_sec,
		event->aen_code,
		event->severity,
		event->retrieved,
		event->repeat_count,
		event->parameter_len,
		event->parameter_data);

	tw_osl_notify_event(ctlr_handle, event);
}



/*
 * Function name:	tw_cli_get_request
 * Description:		Gets a request pkt from the free queue.
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 *			req_pkt -- ptr to OSL built req_pkt, if there's one
 * Output:		None
 * Return value:	ptr to request pkt	-- success
 *			TW_CL_NULL		-- failure
 */
struct tw_cli_req_context *
tw_cli_get_request(struct tw_cli_ctlr_context *ctlr
	)
{
	struct tw_cli_req_context	*req;

	tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	{
		/* Get a free request packet. */
		req = tw_cli_req_q_remove_head(ctlr, TW_CLI_FREE_Q);
	}

	/* Initialize some fields to their defaults. */
	if (req) {
		req->req_handle = TW_CL_NULL;
		req->data = TW_CL_NULL;
		req->length = 0;
		req->data_phys = 0;
		req->state = TW_CLI_REQ_STATE_INIT; /* req being initialized */
		req->flags = 0;
		req->error_code = 0;
		req->orig_req = TW_CL_NULL;
		req->tw_cli_callback = TW_CL_NULL;

		/*
		 * Look at the status field in the command packet to see how
		 * it completed the last time it was used, and zero out only
		 * the portions that might have changed.  Note that we don't
		 * care to zero out the sglist.
		 */
		if (req->cmd_pkt->command.cmd_pkt_9k.status)
			tw_osl_memzero(req->cmd_pkt,
				sizeof(struct tw_cl_command_header) +
				28 /* max bytes before sglist */);
		else
			tw_osl_memzero(&(req->cmd_pkt->command),
				28 /* max bytes before sglist */);

	}
	return(req);
}



/*
 * Function name:	tw_cli_dbg_printf
 * Description:		Calls OSL print function if dbg_level is appropriate
 *
 * Input:		dbg_level -- Determines whether or not to print
 *			ctlr_handle -- controller handle
 *			cur_func -- text name of calling function
 *			fmt -- format string for the arguments to follow
 *			... -- variable number of arguments, to be printed
 *				based on the fmt string
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cli_dbg_printf(TW_UINT8 dbg_level,
	struct tw_cl_ctlr_handle *ctlr_handle, const TW_INT8 *cur_func,
	TW_INT8 *fmt, ...)
{
#ifdef TW_OSL_DEBUG
	TW_INT8	print_str[256];
	va_list	ap;

	tw_osl_memzero(print_str, 256);
	if (dbg_level <= TW_OSL_DEBUG_LEVEL_FOR_CL) {
		tw_osl_sprintf(print_str, "%s: ", cur_func);

		va_start(ap, fmt);
		tw_osl_vsprintf(print_str + tw_osl_strlen(print_str), fmt, ap);
		va_end(ap);

		tw_osl_strcpy(print_str + tw_osl_strlen(print_str), "\n");
		tw_osl_dbg_printf(ctlr_handle, "%s", print_str);
	}
#endif /* TW_OSL_DEBUG */
}



/*
 * Function name:	tw_cli_notify_ctlr_info
 * Description:		Notify OSL of controller info (fw/BIOS versions, etc.).
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cli_notify_ctlr_info(struct tw_cli_ctlr_context *ctlr)
{
	TW_INT8		fw_ver[16];
	TW_INT8		bios_ver[16];
	TW_INT8		ctlr_model[16];
	TW_INT32	error[3];
	TW_UINT8	num_ports = 0;

	tw_cli_dbg_printf(5, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	/* Get the port count. */
	error[0] = tw_cli_get_param(ctlr, TWA_PARAM_CONTROLLER_TABLE,
			TWA_PARAM_CONTROLLER_PORT_COUNT, &num_ports,
			1, TW_CL_NULL);

	/* Get the firmware and BIOS versions. */
	error[0] = tw_cli_get_param(ctlr, TWA_PARAM_VERSION_TABLE,
			TWA_PARAM_VERSION_FW, fw_ver, 16, TW_CL_NULL);
	error[1] = tw_cli_get_param(ctlr, TWA_PARAM_VERSION_TABLE,
			TWA_PARAM_VERSION_BIOS, bios_ver, 16, TW_CL_NULL);
	error[2] = tw_cli_get_param(ctlr, TWA_PARAM_VERSION_TABLE,
			TWA_PARAM_CTLR_MODEL, ctlr_model, 16, TW_CL_NULL);

	tw_cl_create_event(ctlr->ctlr_handle, TW_CL_FALSE,
		TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
		0x1300, 0x3, TW_CL_SEVERITY_INFO_STRING,
		"Controller details:",
		"Model %.16s, %d ports, Firmware %.16s, BIOS %.16s",
		error[2]?(TW_INT8 *)TW_CL_NULL:ctlr_model,
		num_ports,
		error[0]?(TW_INT8 *)TW_CL_NULL:fw_ver,
		error[1]?(TW_INT8 *)TW_CL_NULL:bios_ver);
}



/*
 * Function name:	tw_cli_check_ctlr_state
 * Description:		Makes sure that the fw status register reports a
 *			proper status.
 *
 * Input:		ctlr	-- ptr to CL internal ctlr context
 *			status_reg-- value in the status register
 * Output:		None
 * Return value:	0	-- no errors
 *			non-zero-- errors
 */
TW_INT32
tw_cli_check_ctlr_state(struct tw_cli_ctlr_context *ctlr, TW_UINT32 status_reg)
{
	struct tw_cl_ctlr_handle	*ctlr_handle = ctlr->ctlr_handle;
	TW_INT32			error = TW_OSL_ESUCCESS;

	tw_cli_dbg_printf(8, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");

	/* Check if the 'micro-controller ready' bit is not set. */
	if (!(status_reg & TWA_STATUS_MICROCONTROLLER_READY)) {
		TW_INT8	desc[200];

		tw_osl_memzero(desc, 200);
		if (!(ctlr->reset_phase1_in_progress)) {
			tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
				TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
				0x1301, 0x1, TW_CL_SEVERITY_ERROR_STRING,
				"Missing expected status bit(s)",
				"status reg = 0x%x; Missing bits: %s",
				status_reg,
				tw_cli_describe_bits(
					TWA_STATUS_MICROCONTROLLER_READY,
					desc));
			error = TW_OSL_EGENFAILURE;
		}
	}

	/* Check if any error bits are set. */
	if ((status_reg & TWA_STATUS_UNEXPECTED_BITS) != 0) {
		TW_INT8	desc[200];

		tw_osl_memzero(desc, 200);

		/* Skip queue error msgs during 9650SE/9690SA reset */
		if (((ctlr->device_id != TW_CL_DEVICE_ID_9K_E) &&
		     (ctlr->device_id != TW_CL_DEVICE_ID_9K_SA)) ||
		    (!(ctlr->reset_in_progress)) ||
		    ((status_reg & TWA_STATUS_QUEUE_ERROR_INTERRUPT) == 0))
		tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
			0x1302, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Unexpected status bit(s)",
			"status reg = 0x%x Unexpected bits: %s",
			status_reg & TWA_STATUS_UNEXPECTED_BITS,
			tw_cli_describe_bits(status_reg &
				TWA_STATUS_UNEXPECTED_BITS, desc));

		if (status_reg & TWA_STATUS_PCI_PARITY_ERROR_INTERRUPT) {
			tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
				TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
				0x1303, 0x1, TW_CL_SEVERITY_ERROR_STRING,
				"PCI parity error: clearing... "
				"Re-seat/move/replace card",
				"status reg = 0x%x %s",
				status_reg,
				tw_cli_describe_bits(status_reg, desc));
			TW_CLI_WRITE_CONTROL_REGISTER(ctlr->ctlr_handle,
				TWA_CONTROL_CLEAR_PARITY_ERROR);

#ifdef TW_OSL_PCI_CONFIG_ACCESSIBLE
			tw_osl_write_pci_config(ctlr->ctlr_handle,
				TW_CLI_PCI_CONFIG_STATUS_OFFSET,
				TWA_PCI_CONFIG_CLEAR_PARITY_ERROR, 2);
#endif /* TW_OSL_PCI_CONFIG_ACCESSIBLE */
		
		}

		if (status_reg & TWA_STATUS_PCI_ABORT_INTERRUPT) {
			tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
				TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
				0x1304, 0x1, TW_CL_SEVERITY_ERROR_STRING,
				"PCI abort: clearing... ",
				"status reg = 0x%x %s",
				status_reg,
				tw_cli_describe_bits(status_reg, desc));
			TW_CLI_WRITE_CONTROL_REGISTER(ctlr->ctlr_handle,
				TWA_CONTROL_CLEAR_PCI_ABORT);

#ifdef TW_OSL_PCI_CONFIG_ACCESSIBLE
			tw_osl_write_pci_config(ctlr->ctlr_handle,
				TW_CLI_PCI_CONFIG_STATUS_OFFSET,
				TWA_PCI_CONFIG_CLEAR_PCI_ABORT, 2);
#endif /* TW_OSL_PCI_CONFIG_ACCESSIBLE */

		}

		if (status_reg & TWA_STATUS_QUEUE_ERROR_INTERRUPT) {
			/* Skip queue error msgs during 9650SE/9690SA reset */
			if (((ctlr->device_id != TW_CL_DEVICE_ID_9K_E) &&
			     (ctlr->device_id != TW_CL_DEVICE_ID_9K_SA)) ||
			    (!(ctlr->reset_in_progress)))
				tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
						   TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
						   0x1305, 0x1, TW_CL_SEVERITY_ERROR_STRING,
						   "Controller queue error: clearing... ",
						   "status reg = 0x%x %s",
						   status_reg,
						   tw_cli_describe_bits(status_reg, desc));
			TW_CLI_WRITE_CONTROL_REGISTER(ctlr->ctlr_handle,
				TWA_CONTROL_CLEAR_QUEUE_ERROR);
		}
	}
	return(error);
}	



/*
 * Function name:	tw_cli_describe_bits
 * Description:		Given the value of the status register, returns a
 *			string describing the meaning of each set bit.
 *
 * Input:		reg -- status register value
 * Output:		Pointer to a string describing each set bit
 * Return value:	Pointer to the string describing each set bit
 */
TW_INT8	*
tw_cli_describe_bits(TW_UINT32 reg, TW_INT8 *str)
{
	tw_osl_strcpy(str, "[");

	if (reg & TWA_STATUS_COMMAND_QUEUE_EMPTY)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "CMD_Q_EMPTY,");
	if (reg & TWA_STATUS_MICROCONTROLLER_READY)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "MC_RDY,");
	if (reg & TWA_STATUS_RESPONSE_QUEUE_EMPTY)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "RESP_Q_EMPTY,");
	if (reg & TWA_STATUS_COMMAND_QUEUE_FULL)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "CMD_Q_FULL,");
	if (reg & TWA_STATUS_RESPONSE_INTERRUPT)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "RESP_INTR,");
	if (reg & TWA_STATUS_COMMAND_INTERRUPT)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "CMD_INTR,");
	if (reg & TWA_STATUS_ATTENTION_INTERRUPT)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "ATTN_INTR,");
	if (reg & TWA_STATUS_HOST_INTERRUPT)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "HOST_INTR,");
	if (reg & TWA_STATUS_PCI_ABORT_INTERRUPT)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "PCI_ABRT,");
	if (reg & TWA_STATUS_QUEUE_ERROR_INTERRUPT)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "Q_ERR,");
	if (reg & TWA_STATUS_PCI_PARITY_ERROR_INTERRUPT)
		tw_osl_strcpy(&str[tw_osl_strlen(str)], "PCI_PERR");

	tw_osl_strcpy(&str[tw_osl_strlen(str)], "]");
	return(str);
}



#ifdef TW_OSL_DEBUG

/*
 * Function name:	tw_cl_print_ctlr_stats
 * Description:		Prints the current status of the controller.
 *
 * Input:		ctlr_handle-- controller handle
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cl_print_ctlr_stats(struct tw_cl_ctlr_handle *ctlr_handle)
{
	struct tw_cli_ctlr_context	*ctlr =
		(struct tw_cli_ctlr_context *)(ctlr_handle->cl_ctlr_ctxt);
	TW_UINT32			status_reg;
	TW_INT8				desc[200];

	tw_cli_dbg_printf(7, ctlr->ctlr_handle, "", "entered");

	/* Print current controller details. */
	tw_cli_dbg_printf(0, ctlr_handle, "", "cl_ctlr_ctxt = %p", ctlr);

	tw_osl_memzero(desc, 200);
	status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr_handle);
	tw_cli_dbg_printf(0, ctlr_handle, "", "status reg = 0x%x %s",
		status_reg, tw_cli_describe_bits(status_reg, desc));

	tw_cli_dbg_printf(0, ctlr_handle, "", "CLq type  current  max");
	tw_cli_dbg_printf(0, ctlr_handle, "", "free      %04d     %04d",
		ctlr->q_stats[TW_CLI_FREE_Q].cur_len,
		ctlr->q_stats[TW_CLI_FREE_Q].max_len);
	tw_cli_dbg_printf(0, ctlr_handle, "", "busy      %04d     %04d",
		ctlr->q_stats[TW_CLI_BUSY_Q].cur_len,
		ctlr->q_stats[TW_CLI_BUSY_Q].max_len);
	tw_cli_dbg_printf(0, ctlr_handle, "", "pending   %04d     %04d",
		ctlr->q_stats[TW_CLI_PENDING_Q].cur_len,
		ctlr->q_stats[TW_CLI_PENDING_Q].max_len);
	tw_cli_dbg_printf(0, ctlr_handle, "", "complete  %04d     %04d",
		ctlr->q_stats[TW_CLI_COMPLETE_Q].cur_len,
		ctlr->q_stats[TW_CLI_COMPLETE_Q].max_len);
	tw_cli_dbg_printf(0, ctlr_handle, "", "AEN queue head %d  tail %d",
			ctlr->aen_head, ctlr->aen_tail);
}	



/*
 * Function name:	tw_cl_reset_stats
 * Description:		Resets CL maintained statistics for the controller.
 *
 * Input:		ctlr_handle-- controller handle
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cl_reset_stats(struct tw_cl_ctlr_handle *ctlr_handle)
{
	struct tw_cli_ctlr_context	*ctlr =
		(struct tw_cli_ctlr_context *)(ctlr_handle->cl_ctlr_ctxt);

	tw_cli_dbg_printf(7, ctlr_handle, tw_osl_cur_func(), "entered");
	ctlr->q_stats[TW_CLI_FREE_Q].max_len = 0;
	ctlr->q_stats[TW_CLI_BUSY_Q].max_len = 0;
	ctlr->q_stats[TW_CLI_PENDING_Q].max_len = 0;
	ctlr->q_stats[TW_CLI_COMPLETE_Q].max_len = 0;
}



/*
 * Function name:	tw_cli_print_req_info
 * Description:		Prints CL internal details of a given request.
 *
 * Input:		req	-- ptr to CL internal request context
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cl_print_req_info(struct tw_cl_req_handle *req_handle)
{
	struct tw_cli_req_context	*req = req_handle->cl_req_ctxt;
	struct tw_cli_ctlr_context	*ctlr = req->ctlr;
	struct tw_cl_ctlr_handle	*ctlr_handle = ctlr->ctlr_handle;
	struct tw_cl_command_packet	*cmd_pkt = req->cmd_pkt;
	struct tw_cl_command_9k		*cmd9k;
	union tw_cl_command_7k		*cmd7k;
	TW_UINT8			*cdb;
	TW_VOID				*sgl;
	TW_UINT32			sgl_entries;
	TW_UINT32			i;

	tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
		"CL details for request:");
	tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
		"req_handle = %p, ctlr = %p,\n"
		"cmd_pkt = %p, cmd_pkt_phys = 0x%llx,\n"
		"data = %p, length = 0x%x, data_phys = 0x%llx,\n"
		"state = 0x%x, flags = 0x%x, error = 0x%x,\n"
		"orig_req = %p, callback = %p, req_id = 0x%x,\n"
		"next_req = %p, prev_req = %p",
		req_handle, ctlr,
		cmd_pkt, req->cmd_pkt_phys,
		req->data, req->length, req->data_phys,
		req->state, req->flags, req->error_code,
		req->orig_req, req->tw_cli_callback, req->request_id,
		req->link.next, req->link.prev);

	if (req->flags & TW_CLI_REQ_FLAGS_9K) {
		cmd9k = &(cmd_pkt->command.cmd_pkt_9k);
		sgl = cmd9k->sg_list;
		sgl_entries = TW_CL_SWAP16(
			GET_SGL_ENTRIES(cmd9k->lun_h4__sgl_entries));
		tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
			"9K cmd: opcode = 0x%x, unit = 0x%x, req_id = 0x%x,\n"
			"status = 0x%x, sgl_offset = 0x%x, sgl_entries = 0x%x",
			GET_OPCODE(cmd9k->res__opcode),
			cmd9k->unit,
			TW_CL_SWAP16(GET_REQ_ID(cmd9k->lun_l4__req_id)),
			cmd9k->status,
			cmd9k->sgl_offset,
			sgl_entries);

		cdb = (TW_UINT8 *)(cmd9k->cdb);
		tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
			"CDB: %x %x %x %x %x %x %x %x"
			"%x %x %x %x %x %x %x %x",
			cdb[0], cdb[1], cdb[2], cdb[3],
			cdb[4], cdb[5], cdb[6], cdb[7],
			cdb[8], cdb[9], cdb[10], cdb[11],
			cdb[12], cdb[13], cdb[14], cdb[15]);
	} else {
		cmd7k = &(cmd_pkt->command.cmd_pkt_7k);
		sgl = cmd7k->param.sgl;
		sgl_entries = (cmd7k->generic.size -
			GET_SGL_OFF(cmd7k->generic.sgl_off__opcode)) /
			((ctlr->flags & TW_CL_64BIT_ADDRESSES) ? 3 : 2);
		tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
			"7K cmd: opcode = 0x%x, sgl_offset = 0x%x,\n"
			"size = 0x%x, req_id = 0x%x, unit = 0x%x,\n"
			"status = 0x%x, flags = 0x%x, count = 0x%x",
			GET_OPCODE(cmd7k->generic.sgl_off__opcode),
			GET_SGL_OFF(cmd7k->generic.sgl_off__opcode),
			cmd7k->generic.size,
			TW_CL_SWAP16(cmd7k->generic.request_id),
			GET_UNIT(cmd7k->generic.host_id__unit),
			cmd7k->generic.status,
			cmd7k->generic.flags,
			TW_CL_SWAP16(cmd7k->generic.count));
	}

	tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(), "SG entries:");

	if (ctlr->flags & TW_CL_64BIT_ADDRESSES) {
		struct tw_cl_sg_desc64 *sgl64 = (struct tw_cl_sg_desc64 *)sgl;

		for (i = 0; i < sgl_entries; i++) {
			tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
				"0x%llx  0x%x",
				sgl64[i].address, sgl64[i].length);
		}
	} else {
		struct tw_cl_sg_desc32 *sgl32 = (struct tw_cl_sg_desc32 *)sgl;

		for (i = 0; i < sgl_entries; i++) {
			tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
				"0x%x  0x%x",
				sgl32[i].address, sgl32[i].length);
		}
	}
}

#endif /* TW_OSL_DEBUG */

