/*
 * Copyright (c) 2004-05 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: src/sys/dev/twa/tw_cl_io.c,v 1.2 2005/11/08 22:51:42 vkashyap Exp $
 */

/*
 * AMCC'S 3ware driver for 9000 series storage controllers.
 *
 * Author: Vinod Kashyap
 */


/*
 * Common Layer I/O 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"



/*
 * Function name:	tw_cl_start_io
 * Description:		Interface to OS Layer for accepting SCSI requests.
 *
 * Input:		ctlr_handle	-- controller handle
 *			req_pkt		-- OSL built request packet
 *			req_handle	-- request handle
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cl_start_io(struct tw_cl_ctlr_handle *ctlr_handle,
	struct tw_cl_req_packet *req_pkt, struct tw_cl_req_handle *req_handle)
{
	struct tw_cli_ctlr_context		*ctlr;
	struct tw_cli_req_context		*req;
	struct tw_cl_command_9k			*cmd;
	struct tw_cl_scsi_req_packet		*scsi_req;
	TW_INT32				error;

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

	ctlr = (struct tw_cli_ctlr_context *)(ctlr_handle->cl_ctlr_ctxt);

	if (ctlr->state & TW_CLI_CTLR_STATE_RESET_IN_PROGRESS) {
		tw_cli_dbg_printf(2, ctlr_handle, tw_osl_cur_func(),
			"I/O during reset: returning busy. Ctlr state = 0x%x",
			ctlr->state);
		tw_osl_ctlr_busy(ctlr_handle, req_handle);
		return(TW_OSL_EBUSY);
	}

	/*
	 * If working with a firmware version that does not support multiple
	 * luns, and this request is directed at a non-zero lun, error it
	 * back right away.
	 */
	if ((req_pkt->gen_req_pkt.scsi_req.lun) &&
		(ctlr->working_srl < TWA_MULTI_LUN_FW_SRL)) {
		req_pkt->status |= (TW_CL_ERR_REQ_INVALID_LUN |
			TW_CL_ERR_REQ_SCSI_ERROR);
		req_pkt->tw_osl_callback(req_handle);
		return(TW_CL_ERR_REQ_SUCCESS);
	}

	if ((req = tw_cli_get_request(ctlr
#ifdef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
		, req_pkt
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */
		)) == TW_CL_NULL) {
		tw_cli_dbg_printf(2, ctlr_handle, tw_osl_cur_func(),
			"Out of request context packets: returning busy");
		tw_osl_ctlr_busy(ctlr_handle, req_handle);
		return(TW_OSL_EBUSY);
	}

	req_handle->cl_req_ctxt = req;

#ifdef TW_OSL_DMA_MEM_ALLOC_PER_REQUEST

	req->cmd_pkt = req_pkt->dma_mem;
	req->cmd_pkt_phys = req_pkt->dma_mem_phys;
	tw_osl_memzero(req->cmd_pkt,
		sizeof(struct tw_cl_command_header) +
		28 /* max bytes before sglist */);

#endif /* TW_OSL_DMA_MEM_ALLOC_PER_REQUEST */

	req->req_handle = req_handle;
	req->orig_req = req_pkt;
	req->tw_cli_callback = tw_cli_complete_io;

	req->flags |= TW_CLI_REQ_FLAGS_EXTERNAL;
	req->flags |= TW_CLI_REQ_FLAGS_9K;

	scsi_req = &(req_pkt->gen_req_pkt.scsi_req);

	/* Build the cmd pkt. */
	cmd = &(req->cmd_pkt->command.cmd_pkt_9k);

	req->cmd_pkt->cmd_hdr.header_desc.size_header = 128;

	cmd->res__opcode = BUILD_RES__OPCODE(0, TWA_FW_CMD_EXECUTE_SCSI);
	cmd->unit = (TW_UINT8)(scsi_req->unit);
	cmd->lun_l4__req_id = TW_CL_SWAP16(
		BUILD_LUN_L4__REQ_ID(scsi_req->lun, req->request_id));
	cmd->status = 0;
	cmd->sgl_offset = 16; /* offset from end of hdr = max cdb len */
	tw_osl_memcpy(cmd->cdb, scsi_req->cdb, scsi_req->cdb_len);

	if (req_pkt->flags & TW_CL_REQ_CALLBACK_FOR_SGLIST) {
		TW_UINT32	num_sgl_entries;

		req_pkt->tw_osl_sgl_callback(req_handle, cmd->sg_list,
			&num_sgl_entries);
		cmd->lun_h4__sgl_entries =
			TW_CL_SWAP16(BUILD_LUN_H4__SGL_ENTRIES(scsi_req->lun,
				num_sgl_entries));
	} else {
		cmd->lun_h4__sgl_entries =
			TW_CL_SWAP16(BUILD_LUN_H4__SGL_ENTRIES(scsi_req->lun,
				scsi_req->sgl_entries));
		tw_cli_fill_sg_list(ctlr, scsi_req->sg_list,
			cmd->sg_list, scsi_req->sgl_entries);
	}

	if ((error = tw_cli_submit_cmd(req))) {
		tw_cli_dbg_printf(2, ctlr_handle, tw_osl_cur_func(),
			"Could not start request. request = %p, error = %d",
			req, error);
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	}
	return(error);
}



/*
 * Function name:	tw_cli_submit_cmd
 * Description:		Submits a cmd to firmware.
 *
 * Input:		req	-- ptr to CL internal request context
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_submit_cmd(struct tw_cli_req_context *req)
{
	struct tw_cli_ctlr_context	*ctlr = req->ctlr;
	struct tw_cl_ctlr_handle	*ctlr_handle = ctlr->ctlr_handle;
	TW_UINT32			status_reg;
	TW_INT32			error;
	TW_UINT8			notify_osl_of_ctlr_busy = TW_CL_FALSE;
#ifdef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
	TW_SYNC_HANDLE			sync_handle;
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */

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

	/* Serialize access to the controller cmd queue. */
	tw_osl_get_lock(ctlr_handle, ctlr->io_lock);
#ifdef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
	if (req->flags & TW_CLI_REQ_FLAGS_EXTERNAL) {
		if (!(ctlr->flags & TW_CL_DEFERRED_INTR_USED))
			tw_osl_sync_isr_block(ctlr_handle, &sync_handle);
	} else {
		if (ctlr->flags & TW_CL_DEFERRED_INTR_USED)
			tw_osl_sync_io_block(ctlr_handle, &sync_handle);
	}
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */

	/* Check to see if we can post a command. */
	status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr_handle);
	if ((error = tw_cli_check_ctlr_state(ctlr, status_reg)))
		goto out;

	if (status_reg & TWA_STATUS_COMMAND_QUEUE_FULL) {
		struct tw_cl_req_packet	*req_pkt =
			(struct tw_cl_req_packet *)(req->orig_req);

		tw_cli_dbg_printf(7, ctlr_handle, tw_osl_cur_func(),
			"Cmd queue full");

		if ((req->flags & TW_CLI_REQ_FLAGS_INTERNAL)
#ifndef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
			|| ((req_pkt) &&
			(req_pkt->flags & TW_CL_REQ_RETRY_ON_BUSY))
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */
			) {
			if (req->state != TW_CLI_REQ_STATE_PENDING) {
				tw_cli_dbg_printf(2, ctlr_handle,
					tw_osl_cur_func(),
					"pending internal/ioctl request");
				req->state = TW_CLI_REQ_STATE_PENDING;
				tw_cli_req_q_insert_tail(req, TW_CLI_PENDING_Q);
				error = 0;
			} else
				error = TW_OSL_EBUSY;
		} else {
			notify_osl_of_ctlr_busy = TW_CL_TRUE;
			error = TW_OSL_EBUSY;
		}
	} else {
		tw_cli_dbg_printf(10, ctlr_handle, tw_osl_cur_func(),
			"Submitting command");

		/*
		 * The controller cmd queue is not full.  Mark the request as
		 * currently being processed by the firmware, and move it into
		 * the busy queue.  Then submit the cmd.
		 */
		req->state = TW_CLI_REQ_STATE_BUSY;
		tw_cli_req_q_insert_tail(req, TW_CLI_BUSY_Q);
		TW_CLI_WRITE_COMMAND_QUEUE(ctlr_handle,
			req->cmd_pkt_phys +
			sizeof(struct tw_cl_command_header));
	}

out:
#ifdef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
	if (req->flags & TW_CLI_REQ_FLAGS_EXTERNAL) {
		if (!(ctlr->flags & TW_CL_DEFERRED_INTR_USED))
			tw_osl_sync_isr_unblock(ctlr_handle, &sync_handle);
	} else {
		if (ctlr->flags & TW_CL_DEFERRED_INTR_USED)
			tw_osl_sync_io_unblock(ctlr_handle, &sync_handle);
	}
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */
	tw_osl_free_lock(ctlr_handle, ctlr->io_lock);

	if (status_reg & TWA_STATUS_COMMAND_QUEUE_FULL) {
		if (notify_osl_of_ctlr_busy)
			tw_osl_ctlr_busy(ctlr_handle, req->req_handle);

		/*
		 * Synchronize access between writes to command and control
		 * registers in 64-bit environments, on G66.
		 */
		if (ctlr->state & TW_CLI_CTLR_STATE_G66_WORKAROUND_NEEDED)
			tw_osl_get_lock(ctlr_handle, ctlr->io_lock);

		/* Unmask command interrupt. */
		TW_CLI_WRITE_CONTROL_REGISTER(ctlr_handle,
			TWA_CONTROL_UNMASK_COMMAND_INTERRUPT);

		if (ctlr->state & TW_CLI_CTLR_STATE_G66_WORKAROUND_NEEDED)
			tw_osl_free_lock(ctlr_handle, ctlr->io_lock);
	}

	return(error);
}



/*
 * Function name:	tw_cl_fw_passthru
 * Description:		Interface to OS Layer for accepting firmware
 *			passthru requests.
 * Input:		ctlr_handle	-- controller handle
 *			req_pkt		-- OSL built request packet
 *			req_handle	-- request handle
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cl_fw_passthru(struct tw_cl_ctlr_handle *ctlr_handle,
	struct tw_cl_req_packet *req_pkt, struct tw_cl_req_handle *req_handle)
{
	struct tw_cli_ctlr_context		*ctlr;
	struct tw_cli_req_context		*req;
	union tw_cl_command_7k			*cmd_7k;
	struct tw_cl_command_9k			*cmd_9k;
	struct tw_cl_passthru_req_packet	*pt_req;
	TW_UINT8				opcode;
	TW_UINT8				sgl_offset;
	TW_VOID					*sgl = TW_CL_NULL;
	TW_INT32				error;

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

	ctlr = (struct tw_cli_ctlr_context *)(ctlr_handle->cl_ctlr_ctxt);

	if (ctlr->state & TW_CLI_CTLR_STATE_RESET_IN_PROGRESS) {
		tw_cli_dbg_printf(2, ctlr_handle, tw_osl_cur_func(),
			"Passthru request during reset: returning busy. "
			"Ctlr state = 0x%x",
			ctlr->state);
		tw_osl_ctlr_busy(ctlr_handle, req_handle);
		return(TW_OSL_EBUSY);
	}

	if ((req = tw_cli_get_request(ctlr
#ifdef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
		, req_pkt
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */
		)) == TW_CL_NULL) {
		tw_cli_dbg_printf(2, ctlr_handle, tw_osl_cur_func(),
			"Out of request context packets: returning busy");
		tw_osl_ctlr_busy(ctlr_handle, req_handle);
		return(TW_OSL_EBUSY);
	}

	req_handle->cl_req_ctxt = req;

#ifdef TW_OSL_DMA_MEM_ALLOC_PER_REQUEST

	req->cmd_pkt = req_pkt->dma_mem;
	req->cmd_pkt_phys = req_pkt->dma_mem_phys;
	tw_osl_memzero(req->cmd_pkt,
		sizeof(struct tw_cl_command_header) +
		28 /* max bytes before sglist */);

#endif /* TW_OSL_DMA_MEM_ALLOC_PER_REQUEST */

	req->req_handle = req_handle;
	req->orig_req = req_pkt;
	req->tw_cli_callback = tw_cli_complete_io;

	req->flags |= (TW_CLI_REQ_FLAGS_EXTERNAL | TW_CLI_REQ_FLAGS_PASSTHRU);

	pt_req = &(req_pkt->gen_req_pkt.pt_req);

	tw_osl_memcpy(req->cmd_pkt, pt_req->cmd_pkt,
		pt_req->cmd_pkt_length);
	/* Build the cmd pkt. */
	if ((opcode = GET_OPCODE(((TW_UINT8 *)
		(pt_req->cmd_pkt))[sizeof(struct tw_cl_command_header)]))
			== TWA_FW_CMD_EXECUTE_SCSI) {
		TW_UINT16	lun_l4, lun_h4;

		tw_cli_dbg_printf(5, ctlr_handle, tw_osl_cur_func(),
			"passthru: 9k cmd pkt");
		req->flags |= TW_CLI_REQ_FLAGS_9K;
		cmd_9k = &(req->cmd_pkt->command.cmd_pkt_9k);
		lun_l4 = GET_LUN_L4(cmd_9k->lun_l4__req_id);
		lun_h4 = GET_LUN_H4(cmd_9k->lun_h4__sgl_entries);
		cmd_9k->lun_l4__req_id = TW_CL_SWAP16(
			BUILD_LUN_L4__REQ_ID(lun_l4, req->request_id));
		if (pt_req->sgl_entries) {
			cmd_9k->lun_h4__sgl_entries =
				TW_CL_SWAP16(BUILD_LUN_H4__SGL_ENTRIES(lun_h4,
					pt_req->sgl_entries));
			sgl = (TW_VOID *)(cmd_9k->sg_list);
		}
	} else {
		tw_cli_dbg_printf(5, ctlr_handle, tw_osl_cur_func(),
			"passthru: 7k cmd pkt");
		cmd_7k = &(req->cmd_pkt->command.cmd_pkt_7k);
		cmd_7k->generic.request_id =
			(TW_UINT8)(TW_CL_SWAP16(req->request_id));
		if ((sgl_offset =
			GET_SGL_OFF(cmd_7k->generic.sgl_off__opcode))) {
			sgl = (((TW_UINT32 *)cmd_7k) + sgl_offset);
			cmd_7k->generic.size += pt_req->sgl_entries *
				((ctlr->flags & TW_CL_64BIT_ADDRESSES) ? 3 : 2);
		}
	}

	if (sgl)
		tw_cli_fill_sg_list(ctlr, pt_req->sg_list,
			sgl, pt_req->sgl_entries);

	if ((error = tw_cli_submit_cmd(req))) {
		tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
			0x1100, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Failed to start passthru command",
			"error = %d", error);
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	}
	return(error);
}



/*
 * Function name:	tw_cl_ioctl
 * Description:		Handler of CL supported ioctl cmds.
 *
 * Input:		ctlr	-- ptr to per ctlr structure
 *			cmd	-- ioctl cmd
 *			buf	-- ptr to buffer in kernel memory, which is
 *				   a copy of the input buffer in user-space
 * Output:		buf	-- ptr to buffer in kernel memory, which will
 *				   need to be copied to the output buffer in
 *				   user-space
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cl_ioctl(struct tw_cl_ctlr_handle *ctlr_handle, TW_INT32 cmd, TW_VOID *buf)
{
	struct tw_cli_ctlr_context	*ctlr =
		(struct tw_cli_ctlr_context *)(ctlr_handle->cl_ctlr_ctxt);
	struct tw_cl_ioctl_packet	*user_buf =
		(struct tw_cl_ioctl_packet *)buf;
	struct tw_cl_event_packet	event_buf;
	TW_INT32			event_index;
	TW_INT32			start_index;
	TW_INT32			error = TW_OSL_ESUCCESS;

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

	/* Serialize access to the AEN queue and the ioctl lock. */
	tw_osl_get_lock(ctlr_handle, ctlr->gen_lock);

	switch (cmd) {
	case TW_CL_IOCTL_GET_FIRST_EVENT:
		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Get First Event");

		if (ctlr->aen_q_wrapped) {
			if (ctlr->aen_q_overflow) {
				/*
				 * The aen queue has wrapped, even before some
				 * events have been retrieved.  Let the caller
				 * know that he missed out on some AEN's.
				 */
				user_buf->driver_pkt.status =
					TW_CL_ERROR_AEN_OVERFLOW;
				ctlr->aen_q_overflow = TW_CL_FALSE;
			} else
				user_buf->driver_pkt.status = 0;
			event_index = ctlr->aen_head;
		} else {
			if (ctlr->aen_head == ctlr->aen_tail) {
				user_buf->driver_pkt.status =
					TW_CL_ERROR_AEN_NO_EVENTS;
				break;
			}
			user_buf->driver_pkt.status = 0;
			event_index = ctlr->aen_tail;	/* = 0 */
		}
		tw_osl_memcpy(user_buf->data_buf,
			&(ctlr->aen_queue[event_index]),
			sizeof(struct tw_cl_event_packet));

		ctlr->aen_queue[event_index].retrieved = TW_CL_AEN_RETRIEVED;

		break;


	case TW_CL_IOCTL_GET_LAST_EVENT:
		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Get Last Event");

		if (ctlr->aen_q_wrapped) {
			if (ctlr->aen_q_overflow) {
				/*
				 * The aen queue has wrapped, even before some
				 * events have been retrieved.  Let the caller
				 * know that he missed out on some AEN's.
				 */
				user_buf->driver_pkt.status =
					TW_CL_ERROR_AEN_OVERFLOW;
				ctlr->aen_q_overflow = TW_CL_FALSE;
			} else
				user_buf->driver_pkt.status = 0;
		} else {
			if (ctlr->aen_head == ctlr->aen_tail) {
				user_buf->driver_pkt.status =
					TW_CL_ERROR_AEN_NO_EVENTS;
				break;
			}
			user_buf->driver_pkt.status = 0;
		}
		event_index = (ctlr->aen_head - 1 + ctlr->max_aens_supported) %
			ctlr->max_aens_supported;

		tw_osl_memcpy(user_buf->data_buf,
			&(ctlr->aen_queue[event_index]),
			sizeof(struct tw_cl_event_packet));

		ctlr->aen_queue[event_index].retrieved = TW_CL_AEN_RETRIEVED;
		
		break;


	case TW_CL_IOCTL_GET_NEXT_EVENT:
		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Get Next Event");

		user_buf->driver_pkt.status = 0;
		if (ctlr->aen_q_wrapped) {
			tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
				"Get Next Event: wrapped");
			if (ctlr->aen_q_overflow) {
				/*
				 * The aen queue has wrapped, even before some
				 * events have been retrieved.  Let the caller
				 * know that he missed out on some AEN's.
				 */
				tw_cli_dbg_printf(2, ctlr_handle,
					tw_osl_cur_func(),
					"Get Next Event: overflow");
				user_buf->driver_pkt.status =
					TW_CL_ERROR_AEN_OVERFLOW;
				ctlr->aen_q_overflow = TW_CL_FALSE;
			}
			start_index = ctlr->aen_head;
		} else {
			if (ctlr->aen_head == ctlr->aen_tail) {
				tw_cli_dbg_printf(3, ctlr_handle,
					tw_osl_cur_func(),
					"Get Next Event: empty queue");
				user_buf->driver_pkt.status =
					TW_CL_ERROR_AEN_NO_EVENTS;
				break;
			}
			start_index = ctlr->aen_tail;	/* = 0 */
		}
		tw_osl_memcpy(&event_buf, user_buf->data_buf,
			sizeof(struct tw_cl_event_packet));

		event_index = (start_index + event_buf.sequence_id -
			ctlr->aen_queue[start_index].sequence_id + 1) %
			ctlr->max_aens_supported;

		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Get Next Event: si = %x, ei = %x, ebsi = %x, "
			"sisi = %x, eisi = %x",
			start_index, event_index, event_buf.sequence_id,
			ctlr->aen_queue[start_index].sequence_id,
			ctlr->aen_queue[event_index].sequence_id);

		if (! (ctlr->aen_queue[event_index].sequence_id >
			event_buf.sequence_id)) {
			/*
			 * We don't have any event matching the criterion.  So,
			 * we have to report TW_CL_ERROR_NO_EVENTS.  If we also
			 * encountered an overflow condition above, we cannot
			 * report both conditions during this call.  We choose
			 * to report NO_EVENTS this time, and an overflow the
			 * next time we are called.
			 */
			if (user_buf->driver_pkt.status ==
				TW_CL_ERROR_AEN_OVERFLOW) {
				/*
				 * Make a note so we report the overflow
				 * next time.
				 */
				ctlr->aen_q_overflow = TW_CL_TRUE;
			}
			user_buf->driver_pkt.status = TW_CL_ERROR_AEN_NO_EVENTS;
			break;
		}
		/* Copy the event -- even if there has been an overflow. */
		tw_osl_memcpy(user_buf->data_buf,
			&(ctlr->aen_queue[event_index]),
			sizeof(struct tw_cl_event_packet));

		ctlr->aen_queue[event_index].retrieved = TW_CL_AEN_RETRIEVED;

		break;


	case TW_CL_IOCTL_GET_PREVIOUS_EVENT:
		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Get Previous Event");

		user_buf->driver_pkt.status = 0;
		if (ctlr->aen_q_wrapped) {
			if (ctlr->aen_q_overflow) {
				/*
				 * The aen queue has wrapped, even before some
				 * events have been retrieved.  Let the caller
				 * know that he missed out on some AEN's.
				 */
				user_buf->driver_pkt.status =
					TW_CL_ERROR_AEN_OVERFLOW;
				ctlr->aen_q_overflow = TW_CL_FALSE;
			}
			start_index = ctlr->aen_head;
		} else {
			if (ctlr->aen_head == ctlr->aen_tail) {
				user_buf->driver_pkt.status =
					TW_CL_ERROR_AEN_NO_EVENTS;
				break;
			}
			start_index = ctlr->aen_tail;	/* = 0 */
		}
		tw_osl_memcpy(&event_buf, user_buf->data_buf,
			sizeof(struct tw_cl_event_packet));

		event_index = (start_index + event_buf.sequence_id -
			ctlr->aen_queue[start_index].sequence_id - 1) %
			ctlr->max_aens_supported;

		if (! (ctlr->aen_queue[event_index].sequence_id <
			event_buf.sequence_id)) {
			/*
			 * We don't have any event matching the criterion.  So,
			 * we have to report TW_CL_ERROR_NO_EVENTS.  If we also
			 * encountered an overflow condition above, we cannot
			 * report both conditions during this call.  We choose
			 * to report NO_EVENTS this time, and an overflow the
			 * next time we are called.
			 */
			if (user_buf->driver_pkt.status ==
				TW_CL_ERROR_AEN_OVERFLOW) {
				/*
				 * Make a note so we report the overflow
				 * next time.
				 */
				ctlr->aen_q_overflow = TW_CL_TRUE;
			}
			user_buf->driver_pkt.status = TW_CL_ERROR_AEN_NO_EVENTS;
			break;
		}
		/* Copy the event -- even if there has been an overflow. */
		tw_osl_memcpy(user_buf->data_buf,
			&(ctlr->aen_queue[event_index]),
			sizeof(struct tw_cl_event_packet));

		ctlr->aen_queue[event_index].retrieved = TW_CL_AEN_RETRIEVED;

		break;


	case TW_CL_IOCTL_GET_LOCK:
	{
		struct tw_cl_lock_packet	lock_pkt;
		TW_TIME				cur_time;

		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Get ioctl lock");

		cur_time = tw_osl_get_local_time();
		tw_osl_memcpy(&lock_pkt, user_buf->data_buf,
			sizeof(struct tw_cl_lock_packet));

		if ((ctlr->ioctl_lock.lock == TW_CLI_LOCK_FREE) ||
			(lock_pkt.force_flag) ||
			(cur_time >= ctlr->ioctl_lock.timeout)) {
			tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
				"GET_LOCK: Getting lock!");
			ctlr->ioctl_lock.lock = TW_CLI_LOCK_HELD;
			ctlr->ioctl_lock.timeout =
				cur_time + (lock_pkt.timeout_msec / 1000);
			lock_pkt.time_remaining_msec = lock_pkt.timeout_msec;
			user_buf->driver_pkt.status = 0;
		} else {
			tw_cli_dbg_printf(2, ctlr_handle, tw_osl_cur_func(),
				"GET_LOCK: Lock already held!");
			lock_pkt.time_remaining_msec = (TW_UINT32)(
				(ctlr->ioctl_lock.timeout - cur_time) * 1000);
			user_buf->driver_pkt.status =
				TW_CL_ERROR_IOCTL_LOCK_ALREADY_HELD;
		}
		tw_osl_memcpy(user_buf->data_buf, &lock_pkt,
			sizeof(struct tw_cl_lock_packet));
		break;
	}


	case TW_CL_IOCTL_RELEASE_LOCK:
		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Release ioctl lock");

		if (ctlr->ioctl_lock.lock == TW_CLI_LOCK_FREE) {
			tw_cli_dbg_printf(2, ctlr_handle, tw_osl_cur_func(),
				"twa_ioctl: RELEASE_LOCK: Lock not held!");
			user_buf->driver_pkt.status =
				TW_CL_ERROR_IOCTL_LOCK_NOT_HELD;
		} else {
			tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
				"RELEASE_LOCK: Releasing lock!");
			ctlr->ioctl_lock.lock = TW_CLI_LOCK_FREE;
			user_buf->driver_pkt.status = 0;
		}
		break;


	case TW_CL_IOCTL_GET_COMPATIBILITY_INFO:
	{
		struct tw_cl_compatibility_packet	comp_pkt;

		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Get compatibility info");

		tw_osl_memcpy(comp_pkt.driver_version,
			TW_OSL_DRIVER_VERSION_STRING,
			sizeof(TW_OSL_DRIVER_VERSION_STRING));
		comp_pkt.working_srl = ctlr->working_srl;
		comp_pkt.working_branch = ctlr->working_branch;
		comp_pkt.working_build = ctlr->working_build;
		comp_pkt.driver_srl_high = TWA_CURRENT_FW_SRL;
		comp_pkt.driver_branch_high =
			TWA_CURRENT_FW_BRANCH(ctlr->arch_id);
		comp_pkt.driver_build_high =
			TWA_CURRENT_FW_BUILD(ctlr->arch_id);
		comp_pkt.driver_srl_low = TWA_BASE_FW_SRL;
		comp_pkt.driver_branch_low = TWA_BASE_FW_BRANCH;
		comp_pkt.driver_build_high = TWA_BASE_FW_BUILD;
		comp_pkt.fw_on_ctlr_srl = ctlr->fw_on_ctlr_srl;
		comp_pkt.fw_on_ctlr_branch = ctlr->fw_on_ctlr_branch;
		comp_pkt.fw_on_ctlr_build = ctlr->fw_on_ctlr_build;
		user_buf->driver_pkt.status = 0;

		/* Copy compatibility information to user space. */
		tw_osl_memcpy(user_buf->data_buf, &comp_pkt,
			(sizeof(struct tw_cl_compatibility_packet) <
			user_buf->driver_pkt.buffer_length) ?
			sizeof(struct tw_cl_compatibility_packet) :
			user_buf->driver_pkt.buffer_length);
		break;
	}

	default:	
		/* Unknown opcode. */
		tw_cli_dbg_printf(3, ctlr_handle, tw_osl_cur_func(),
			"Unknown ioctl cmd 0x%x", cmd);
		error = TW_OSL_ENOTTY;
	}

	tw_osl_free_lock(ctlr_handle, ctlr->gen_lock);
	return(error);
}



/*
 * Function name:	tw_cli_get_param
 * Description:		Get a firmware parameter.
 *
 * Input:		ctlr		-- ptr to per ctlr structure
 *			table_id	-- parameter table #
 *			param_id	-- index of the parameter in the table
 *			param_size	-- size of the parameter in bytes
 *			callback	-- ptr to function, if any, to be called
 *					back on completion; TW_CL_NULL if no callback.
 * Output:		param_data	-- param value
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_get_param(struct tw_cli_ctlr_context *ctlr, TW_INT32 table_id,
	TW_INT32 param_id, TW_VOID *param_data, TW_INT32 param_size,
	TW_VOID (* callback)(struct tw_cli_req_context *req))
{
	struct tw_cli_req_context	*req;
	union tw_cl_command_7k		*cmd;
	struct tw_cl_param_9k		*param = TW_CL_NULL;
	TW_INT32			error = TW_OSL_EBUSY;

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

	/* Get a request packet. */
	if ((req = tw_cli_get_request(ctlr
#ifdef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
		, TW_CL_NULL
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */
		)) == TW_CL_NULL)
		goto out;

	/* Make sure this is the only CL internal request at this time. */
	if (ctlr->state & TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY) {
		error = TW_OSL_EBUSY;
		goto out;
	}
	ctlr->state |= TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;

#ifdef TW_OSL_DMA_MEM_ALLOC_PER_REQUEST

	req->cmd_pkt = ctlr->cmd_pkt_buf;
	req->cmd_pkt_phys = ctlr->cmd_pkt_phys;
	tw_osl_memzero(req->cmd_pkt,
		sizeof(struct tw_cl_command_header) +
		28 /* max bytes before sglist */);

#endif /* TW_OSL_DMA_MEM_ALLOC_PER_REQUEST */

	req->data = ctlr->internal_req_data;
	req->data_phys = ctlr->internal_req_data_phys;
	req->length = TW_CLI_SECTOR_SIZE;
	req->flags |= TW_CLI_REQ_FLAGS_INTERNAL;

	/* Initialize memory to read data into. */
	param = (struct tw_cl_param_9k *)(req->data);
	tw_osl_memzero(param, sizeof(struct tw_cl_param_9k) - 1 + param_size);

	/* Build the cmd pkt. */
	cmd = &(req->cmd_pkt->command.cmd_pkt_7k);

	req->cmd_pkt->cmd_hdr.header_desc.size_header = 128;

	cmd->param.sgl_off__opcode =
		BUILD_SGL_OFF__OPCODE(2, TWA_FW_CMD_GET_PARAM);
	cmd->param.request_id =
		(TW_UINT8)(TW_CL_SWAP16(req->request_id));
	cmd->param.host_id__unit = BUILD_HOST_ID__UNIT(0, 0);
	cmd->param.param_count = TW_CL_SWAP16(1);

	if (ctlr->flags & TW_CL_64BIT_ADDRESSES) {
		((struct tw_cl_sg_desc64 *)(cmd->param.sgl))[0].address =
			TW_CL_SWAP64(req->data_phys);
		((struct tw_cl_sg_desc64 *)(cmd->param.sgl))[0].length =
			TW_CL_SWAP32(req->length);
		cmd->param.size = 2 + 3;
	} else {
		((struct tw_cl_sg_desc32 *)(cmd->param.sgl))[0].address =
			TW_CL_SWAP32(req->data_phys);
		((struct tw_cl_sg_desc32 *)(cmd->param.sgl))[0].length =
			TW_CL_SWAP32(req->length);
		cmd->param.size = 2 + 2;
	}

	/* Specify which parameter we need. */
	param->table_id = TW_CL_SWAP16(table_id | TWA_9K_PARAM_DESCRIPTOR);
	param->parameter_id = (TW_UINT8)(param_id);
	param->parameter_size_bytes = TW_CL_SWAP16(param_size);

	/* Submit the command. */
	if (callback == TW_CL_NULL) {
		/* There's no call back; wait till the command completes. */
		error = tw_cli_submit_and_poll_request(req,
				TW_CLI_REQUEST_TIMEOUT_PERIOD);
		if (error == TW_OSL_ETIMEDOUT)
			/* Clean-up done by tw_cli_submit_and_poll_request. */
			return(error);
		if (error)
			goto out;
		if ((error = cmd->param.status)) {
			tw_cli_create_ctlr_event(ctlr,
				TW_CL_MESSAGE_SOURCE_CONTROLLER_ERROR,
				&(req->cmd_pkt->cmd_hdr));
			goto out;
		}
		tw_osl_memcpy(param_data, param->data, param_size);
		ctlr->state &= ~TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	} else {
		/* There's a call back.  Simply submit the command. */
		req->tw_cli_callback = callback;
		if ((error = tw_cli_submit_cmd(req)))
			goto out;
	}
	return(0);

out:
	tw_cl_create_event(ctlr->ctlr_handle, TW_CL_FALSE,
		TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
		0x1101, 0x1, TW_CL_SEVERITY_ERROR_STRING,
		"get_param failed",
		"error = %d", error);
	if (param)
		ctlr->state &= ~TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;
	if (req)
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	return(1);
}



/*
 * Function name:	tw_cli_set_param
 * Description:		Set a firmware parameter.
 *
 * Input:		ctlr		-- ptr to per ctlr structure
 *			table_id	-- parameter table #
 *			param_id	-- index of the parameter in the table
 *			param_size	-- size of the parameter in bytes
 *			callback	-- ptr to function, if any, to be called
 *					back on completion; TW_CL_NULL if no callback.
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_set_param(struct tw_cli_ctlr_context *ctlr, TW_INT32 table_id,
	TW_INT32 param_id, TW_INT32 param_size, TW_VOID *data,
	TW_VOID (* callback)(struct tw_cli_req_context *req))
{
	struct tw_cli_req_context	*req;
	union tw_cl_command_7k		*cmd;
	struct tw_cl_param_9k		*param = TW_CL_NULL;
	TW_INT32			error = TW_OSL_EBUSY;

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

	/* Get a request packet. */
	if ((req = tw_cli_get_request(ctlr
#ifdef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
		, TW_CL_NULL
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */
		)) == TW_CL_NULL)
		goto out;

	/* Make sure this is the only CL internal request at this time. */
	if (ctlr->state & TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY) {
		error = TW_OSL_EBUSY;
		goto out;
	}
	ctlr->state |= TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;

#ifdef TW_OSL_DMA_MEM_ALLOC_PER_REQUEST

	req->cmd_pkt = ctlr->cmd_pkt_buf;
	req->cmd_pkt_phys = ctlr->cmd_pkt_phys;
	tw_osl_memzero(req->cmd_pkt,
		sizeof(struct tw_cl_command_header) +
		28 /* max bytes before sglist */);

#endif /* TW_OSL_DMA_MEM_ALLOC_PER_REQUEST */

	req->data = ctlr->internal_req_data;
	req->data_phys = ctlr->internal_req_data_phys;
	req->length = TW_CLI_SECTOR_SIZE;
	req->flags |= TW_CLI_REQ_FLAGS_INTERNAL;

	/* Initialize memory to send data using. */
	param = (struct tw_cl_param_9k *)(req->data);
	tw_osl_memzero(param, sizeof(struct tw_cl_param_9k) - 1 + param_size);

	/* Build the cmd pkt. */
	cmd = &(req->cmd_pkt->command.cmd_pkt_7k);

	req->cmd_pkt->cmd_hdr.header_desc.size_header = 128;

	cmd->param.sgl_off__opcode =
		BUILD_SGL_OFF__OPCODE(2, TWA_FW_CMD_SET_PARAM);
	cmd->param.request_id = (TW_UINT8)(TW_CL_SWAP16(req->request_id));
	cmd->param.host_id__unit = BUILD_HOST_ID__UNIT(0, 0);
	cmd->param.param_count = TW_CL_SWAP16(1);

	if (ctlr->flags & TW_CL_64BIT_ADDRESSES) {
		((struct tw_cl_sg_desc64 *)(cmd->param.sgl))[0].address =
			TW_CL_SWAP64(req->data_phys);
		((struct tw_cl_sg_desc64 *)(cmd->param.sgl))[0].length =
			TW_CL_SWAP32(req->length);
		cmd->param.size = 2 + 3;
	} else {
		((struct tw_cl_sg_desc32 *)(cmd->param.sgl))[0].address =
			TW_CL_SWAP32(req->data_phys);
		((struct tw_cl_sg_desc32 *)(cmd->param.sgl))[0].length =
			TW_CL_SWAP32(req->length);
		cmd->param.size = 2 + 2;
	}

	/* Specify which parameter we want to set. */
	param->table_id = TW_CL_SWAP16(table_id | TWA_9K_PARAM_DESCRIPTOR);
	param->parameter_id = (TW_UINT8)(param_id);
	param->parameter_size_bytes = TW_CL_SWAP16(param_size);
	tw_osl_memcpy(param->data, data, param_size);

	/* Submit the command. */
	if (callback == TW_CL_NULL) {
		/* There's no call back;  wait till the command completes. */
		error = tw_cli_submit_and_poll_request(req,
			TW_CLI_REQUEST_TIMEOUT_PERIOD);
		if (error == TW_OSL_ETIMEDOUT)
			/* Clean-up done by tw_cli_submit_and_poll_request. */
			return(error);
		if (error)
			goto out;
		if ((error = cmd->param.status)) {
			tw_cli_create_ctlr_event(ctlr,
				TW_CL_MESSAGE_SOURCE_CONTROLLER_ERROR,
				&(req->cmd_pkt->cmd_hdr));
			goto out;
		}
		ctlr->state &= ~TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	} else {
		/* There's a call back.  Simply submit the command. */
		req->tw_cli_callback = callback;
		if ((error = tw_cli_submit_cmd(req)))
			goto out;
	}
	return(error);

out:
	tw_cl_create_event(ctlr->ctlr_handle, TW_CL_FALSE,
		TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
		0x1102, 0x1, TW_CL_SEVERITY_ERROR_STRING,
		"set_param failed",
		"error = %d", error);
	if (param)
		ctlr->state &= ~TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;
	if (req)
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	return(error);
}



/*
 * Function name:	tw_cli_submit_and_poll_request
 * Description:		Sends down a firmware cmd, and waits for the completion
 *			in a tight loop.
 *
 * Input:		req	-- ptr to request pkt
 *			timeout -- max # of seconds to wait before giving up
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_submit_and_poll_request(struct tw_cli_req_context *req,
	TW_UINT32 timeout)
{
	struct tw_cli_ctlr_context	*ctlr = req->ctlr;
	TW_TIME				end_time;
	TW_INT32			error;

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

	/*
	 * If the cmd queue is full, tw_cli_submit_cmd will queue this
	 * request in the pending queue, since this is an internal request.
	 */
	if ((error = tw_cli_submit_cmd(req))) {
		tw_cl_create_event(ctlr->ctlr_handle, TW_CL_FALSE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
			0x1103, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Failed to start internal request",
			"error = %d", error);
		return(error);
	}

	/*
	 * Poll for the response until the command gets completed, or there's
	 * a timeout.
	 */
	end_time = tw_osl_get_local_time() + timeout;
	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.
			 * The caller should do the clean-up.
			 */
			return(error);

		/* See if the command completed. */
		tw_cli_process_resp_intr(ctlr);

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

	/* Time out! */
	tw_cl_create_event(ctlr->ctlr_handle, TW_CL_FALSE,
		TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
		0x1104, 0x1, TW_CL_SEVERITY_ERROR_STRING,
		"Internal request timed out",
		"request = %p", req);

	/*
	 * We will reset the controller only if the request has already been
	 * submitted, so as to not lose the request packet.  If a busy request
	 * timed out, the reset will take care of freeing resources.  If a
	 * pending request timed out, we will free resources for that request,
	 * right here, thereby avoiding a reset.  So, the caller is expected
	 * to NOT cleanup when TW_OSL_ETIMEDOUT is returned.
	 */

	/*
	 * We have to make sure that this timed out request, if it were in the
	 * pending queue, doesn't get submitted while we are here, from
	 * tw_cli_submit_pending_queue.  There could be a race in that case.
	 * Need to revisit.
	 */
	if (req->state != TW_CLI_REQ_STATE_PENDING)
		tw_cl_reset_ctlr(ctlr->ctlr_handle);
	else {
		tw_cli_dbg_printf(3, ctlr->ctlr_handle, tw_osl_cur_func(),
			"Removing request from pending queue");
		/*
		 * Request was never submitted.  Clean up.  Note that we did
		 * not do a reset.  So, we have to remove the request ourselves
		 * from the pending queue (as against tw_cli_drain_pendinq_queue
		 * taking care of it).
		 */
		tw_cli_req_q_remove_item(req, TW_CLI_PENDING_Q);
		if (req->data)
			ctlr->state &= ~TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	}

	return(TW_OSL_ETIMEDOUT);
}



/*
 * Function name:	tw_cl_reset_ctlr
 * Description:		Soft resets and then initializes the controller;
 *			drains any incomplete requests.
 *
 * Input:		ctlr	-- ptr to per ctlr structure
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cl_reset_ctlr(struct tw_cl_ctlr_handle *ctlr_handle)
{
	struct tw_cli_ctlr_context	*ctlr =
		(struct tw_cli_ctlr_context *)(ctlr_handle->cl_ctlr_ctxt);
	TW_INT32			reset_attempt = 1;
	TW_INT32			error;

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

	ctlr->state |= TW_CLI_CTLR_STATE_RESET_IN_PROGRESS;

	/*
	 * Error back all requests in the complete, busy, and pending queues.
	 * If any request is already on its way to getting submitted, it's in
	 * none of these queues and so, will not be completed.  That request
	 * will continue its course and get submitted to the controller after
	 * the reset is done (and io_lock is released).
	 */
	tw_cli_dbg_printf(2, ctlr_handle, tw_osl_cur_func(),
		"Draining all queues following reset");
	tw_cli_drain_complete_queue(ctlr);
	tw_cli_drain_busy_queue(ctlr);
	tw_cli_drain_pending_queue(ctlr);

	tw_cli_disable_interrupts(ctlr);

	/* Soft reset the controller. */
try_reset:
	if ((error = tw_cli_soft_reset(ctlr))) {
		tw_cl_create_event(ctlr_handle, TW_CL_TRUE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
			0x1105, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Controller reset failed",
			"error = %d; attempt %d", error, reset_attempt++);
		if (reset_attempt <= TW_CLI_MAX_RESET_ATTEMPTS)
			goto try_reset;
		else
			goto out;
	}

	/* Re-establish logical connection with the controller. */
	if ((error = tw_cli_init_connection(ctlr,
			(TW_UINT16)(ctlr->max_simult_reqs),
			0, 0, 0, 0, 0, TW_CL_NULL, TW_CL_NULL, TW_CL_NULL,
			TW_CL_NULL, TW_CL_NULL))) {
		tw_cl_create_event(ctlr_handle, TW_CL_TRUE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
			0x1106, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Can't initialize connection after reset",
			"error = %d", error);
		goto out;
	}

	tw_cl_create_event(ctlr_handle, TW_CL_TRUE,
		TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
		0x1107, 0x3, TW_CL_SEVERITY_INFO_STRING,
		"Controller reset done!",
		" ");

out:
	ctlr->state &= ~TW_CLI_CTLR_STATE_RESET_IN_PROGRESS;
	/*
	 * Enable interrupts, and also clear attention and response interrupts.
	 */
	tw_cli_enable_interrupts(ctlr);
	
	/* Request for a bus re-scan. */
	if (!error)
		tw_osl_scan_bus(ctlr_handle);
	return(error);
}



/*
 * Function name:	tw_cli_soft_reset
 * Description:		Does the actual soft reset.
 *
 * Input:		ctlr	-- ptr to per ctlr structure
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_soft_reset(struct tw_cli_ctlr_context *ctlr)
{
	struct tw_cl_ctlr_handle	*ctlr_handle = ctlr->ctlr_handle;
	TW_UINT32			status_reg;
	TW_UINT32			error;

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

	tw_cl_create_event(ctlr_handle, TW_CL_TRUE,
		TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
		0x1108, 0x3, TW_CL_SEVERITY_INFO_STRING,
		"Resetting controller...",
		" ");

	/* Don't let any new commands get submitted to the controller. */
	tw_osl_get_lock(ctlr_handle, ctlr->io_lock);

	TW_CLI_SOFT_RESET(ctlr_handle);

	if (ctlr->device_id == TW_CL_DEVICE_ID_9K_X) {
		/*
		 * There's a hardware bug in the G133 ASIC, which can lead to
		 * PCI parity errors and hangs, if the host accesses any
		 * registers when the firmware is resetting the hardware, as
		 * part of a hard/soft reset.  The window of time when the
		 * problem can occur is about 10 ms.  Here, we will handshake
		 * with the firmware to find out when the firmware is pulling
		 * down the hardware reset pin, and wait for about 500 ms to
		 * make sure we don't access any hardware registers (for
		 * polling) during that window.
		 */
		ctlr->state |= TW_CLI_CTLR_STATE_RESET_PHASE1_IN_PROGRESS;
		while (tw_cli_find_response(ctlr,
			TWA_RESET_PHASE1_NOTIFICATION_RESPONSE) != TW_OSL_ESUCCESS)
			tw_osl_delay(10);
		tw_osl_delay(TWA_RESET_PHASE1_WAIT_TIME_MS * 1000);
		ctlr->state &= ~TW_CLI_CTLR_STATE_RESET_PHASE1_IN_PROGRESS;
	}

	if ((error = tw_cli_poll_status(ctlr,
			TWA_STATUS_MICROCONTROLLER_READY |
			TWA_STATUS_ATTENTION_INTERRUPT,
			TW_CLI_RESET_TIMEOUT_PERIOD))) {
		tw_cl_create_event(ctlr_handle, TW_CL_TRUE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
			0x1109, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Micro-ctlr not ready/No attn intr after reset",
			"error = %d", error);
		tw_osl_free_lock(ctlr_handle, ctlr->io_lock);
		return(error);
	}

	TW_CLI_WRITE_CONTROL_REGISTER(ctlr_handle,
		TWA_CONTROL_CLEAR_ATTENTION_INTERRUPT);

	if ((error = tw_cli_drain_response_queue(ctlr))) {
		tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
			0x110A, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Can't drain response queue after reset",
			"error = %d", error);
		tw_osl_free_lock(ctlr_handle, ctlr->io_lock);
		return(error);
	}
	
	tw_osl_free_lock(ctlr_handle, ctlr->io_lock);

	if ((error = tw_cli_drain_aen_queue(ctlr))) {
		tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
			0x110B, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Can't drain AEN queue after reset",
			"error = %d", error);
		return(error);
	}
	
	if ((error = tw_cli_find_aen(ctlr, TWA_AEN_SOFT_RESET))) {
		tw_cl_create_event(ctlr_handle, TW_CL_TRUE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
			0x110C, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Reset not reported by controller",
			"error = %d", error);
		return(error);
	}
	
	status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr_handle);
	
	if ((error = TW_CLI_STATUS_ERRORS(status_reg)) ||
			(error = tw_cli_check_ctlr_state(ctlr, status_reg))) {
		tw_cl_create_event(ctlr_handle, TW_CL_TRUE,
			TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
			0x110D, 0x1, TW_CL_SEVERITY_ERROR_STRING,
			"Controller errors detected after reset",
			"error = %d", error);
		return(error);
	}
	
	return(TW_OSL_ESUCCESS);
}



/*
 * Function name:	tw_cli_send_scsi_cmd
 * Description:		Sends down a scsi cmd to fw.
 *
 * Input:		req	-- ptr to request pkt
 *			cmd	-- opcode of scsi cmd to send
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_send_scsi_cmd(struct tw_cli_req_context *req, TW_INT32 cmd)
{
	struct tw_cl_command_packet	*cmdpkt;
	struct tw_cl_command_9k		*cmd9k;
	struct tw_cli_ctlr_context	*ctlr;
	TW_INT32			error;

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

	/* Make sure this is the only CL internal request at this time. */
	if (ctlr->state & TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY)
		return(TW_OSL_EBUSY);
	ctlr->state |= TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;
	req->data = ctlr->internal_req_data;
	req->data_phys = ctlr->internal_req_data_phys;
	tw_osl_memzero(req->data, TW_CLI_SECTOR_SIZE);
	req->length = TW_CLI_SECTOR_SIZE;

	/* Build the cmd pkt. */
	cmdpkt = req->cmd_pkt;

	cmdpkt->cmd_hdr.header_desc.size_header = 128;
		
	cmd9k = &(cmdpkt->command.cmd_pkt_9k);

	cmd9k->res__opcode =
		BUILD_RES__OPCODE(0, TWA_FW_CMD_EXECUTE_SCSI);
	cmd9k->unit = 0;
	cmd9k->lun_l4__req_id = TW_CL_SWAP16(req->request_id);
	cmd9k->status = 0;
	cmd9k->sgl_offset = 16; /* offset from end of hdr = max cdb len */
	cmd9k->lun_h4__sgl_entries = TW_CL_SWAP16(1);

	if (req->ctlr->flags & TW_CL_64BIT_ADDRESSES) {
		((struct tw_cl_sg_desc64 *)(cmd9k->sg_list))[0].address =
			TW_CL_SWAP64(req->data_phys);
		((struct tw_cl_sg_desc64 *)(cmd9k->sg_list))[0].length =
			TW_CL_SWAP32(req->length);
	} else {
		((struct tw_cl_sg_desc32 *)(cmd9k->sg_list))[0].address =
			TW_CL_SWAP32(req->data_phys);
		((struct tw_cl_sg_desc32 *)(cmd9k->sg_list))[0].length =
			TW_CL_SWAP32(req->length);
	}

	cmd9k->cdb[0] = (TW_UINT8)cmd;
	cmd9k->cdb[4] = 128;

	if ((error = tw_cli_submit_cmd(req)))
		if (error != TW_OSL_EBUSY) {
			tw_cli_dbg_printf(1, ctlr->ctlr_handle,
				tw_osl_cur_func(),
				"Failed to start SCSI command",
				"request = %p, error = %d", req, error);
			return(TW_OSL_EIO);
		}
	return(TW_OSL_ESUCCESS);
}



/*
 * Function name:	tw_cli_get_aen
 * Description:		Sends down a Request Sense cmd to fw to fetch an AEN.
 *
 * Input:		ctlr	-- ptr to per ctlr structure
 * Output:		None
 * Return value:	0	-- success
 *			non-zero-- failure
 */
TW_INT32
tw_cli_get_aen(struct tw_cli_ctlr_context *ctlr)
{
	struct tw_cli_req_context	*req;
	TW_INT32			error;

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

	if ((req = tw_cli_get_request(ctlr
#ifdef TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST
		, TW_CL_NULL
#endif /* TW_OSL_NON_DMA_MEM_ALLOC_PER_REQUEST */
		)) == TW_CL_NULL)
		return(TW_OSL_EBUSY);

#ifdef TW_OSL_DMA_MEM_ALLOC_PER_REQUEST

	req->cmd_pkt = ctlr->cmd_pkt_buf;
	req->cmd_pkt_phys = ctlr->cmd_pkt_phys;
	tw_osl_memzero(req->cmd_pkt,
		sizeof(struct tw_cl_command_header) +
		28 /* max bytes before sglist */);

#endif /* TW_OSL_DMA_MEM_ALLOC_PER_REQUEST */

	req->flags |= TW_CLI_REQ_FLAGS_INTERNAL;
	req->flags |= TW_CLI_REQ_FLAGS_9K;
	req->tw_cli_callback = tw_cli_aen_callback;
	if ((error = tw_cli_send_scsi_cmd(req, 0x03 /* REQUEST_SENSE */))) {
		tw_cli_dbg_printf(1, ctlr->ctlr_handle, tw_osl_cur_func(),
			"Could not send SCSI command",
			"request = %p, error = %d", req, error);
		if (req->data)
			ctlr->state &= ~TW_CLI_CTLR_STATE_INTERNAL_REQ_BUSY;
		tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
	}
	return(error);
}



/*
 * Function name:	tw_cli_fill_sg_list
 * Description:		Fills in the scatter/gather list.
 *
 * Input:		ctlr	-- ptr to per ctlr structure
 *			sgl_src	-- ptr to fill the sg list from
 *			sgl_dest-- ptr to sg list
 *			nsegments--# of segments
 * Output:		None
 * Return value:	None
 */
TW_VOID
tw_cli_fill_sg_list(struct tw_cli_ctlr_context *ctlr, TW_VOID *sgl_src,
	TW_VOID *sgl_dest, TW_INT32 num_sgl_entries)
{
	TW_INT32	i;

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

	if (ctlr->flags & TW_CL_64BIT_ADDRESSES) {
		struct tw_cl_sg_desc64 *sgl_s =
			(struct tw_cl_sg_desc64 *)sgl_src;
		struct tw_cl_sg_desc64 *sgl_d =
			(struct tw_cl_sg_desc64 *)sgl_dest;

		tw_cli_dbg_printf(10, ctlr->ctlr_handle, tw_osl_cur_func(),
			"64 bit addresses");
		for (i = 0; i < num_sgl_entries; i++) {
			sgl_d[i].address = TW_CL_SWAP64(sgl_s->address);
			sgl_d[i].length = TW_CL_SWAP32(sgl_s->length);
			sgl_s++;
			if (ctlr->flags & TW_CL_64BIT_SG_LENGTH)
				sgl_s = (struct tw_cl_sg_desc64 *)
					(((TW_INT8 *)(sgl_s)) + 4);
		}
	} else {
		struct tw_cl_sg_desc32 *sgl_s =
			(struct tw_cl_sg_desc32 *)sgl_src;
		struct tw_cl_sg_desc32 *sgl_d =
			(struct tw_cl_sg_desc32 *)sgl_dest;

		tw_cli_dbg_printf(10, ctlr->ctlr_handle, tw_osl_cur_func(),
			"32 bit addresses");
		for (i = 0; i < num_sgl_entries; i++) {
			sgl_d[i].address = TW_CL_SWAP32(sgl_s[i].address);
			sgl_d[i].length = TW_CL_SWAP32(sgl_s[i].length);
		}
	}
}

