/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
 */

/*
 * This  File  has  Modules  that  handle  the NOE  functionality  for
 *	this driver.
 *	It  builds and  submits  the NOE  command to  the adapter.  It also
 *	processes a completed NOE command.
 *	A study of the FirmWare specifications would be neccessary to relate
 *	coding in this module to the hardware functionality.
 */

#include "cpqary3.h"

/*
 * Local Functions Definitions
 */

uint8_t cpqary3_disable_NOE_command(cpqary3_t *);

/*
 * Last reason a drive at this position was failed by the
 * controller firmware (saved in the RIS).
 */

#define	MAX_KNOWN_FAILURE_REASON	31

char *ascii_failure_reason[] = {
	"NONE",
	"TOO_SMALL_IN_LOAD_CONFIG",
	"ERROR_ERASING_RIS",
	"ERROR_SAVING_RIS",
	"FAIL_DRIVE_COMMAND",
	"MARK_BAD_FAILED",
	"MARK_BAD_FAILED_IN_FINISH_REMAP",
	"TIMEOUT",
	"AUTOSENSE_FAILED",
	"MEDIUM_ERROR_1",
	"MEDIUM_ERROR_2",
	"NOT_READY_BAD_SENSE",
	"NOT_READY",
	"HARDWARE_ERROR",
	"ABORTED_COMMAND",
	"WRITE_PROTECTED",
	"SPIN_UP_FAILURE_IN_RECOVER",
	"REBUILD_WRITE_ERROR",
	"TOO_SMALL_IN_HOT_PLUG",
	"RESET_RECOVERY_ABORT",
	"REMOVED_IN_HOT_PLUG",
	"INIT_REQUEST_SENSE_FAILED",
	"INIT_START_UNIT_FAILED",
	"GDP_INQUIRY_FAILED",
	"GDP_NON_DISK_DEVICE",
	"GDP_READ_CAPACITY_FAILED",
	"GDP_INVALID_BLOCK_SIZE",
	"HOTP_REQUEST_SENSE_FAILED",
	"HOTP_START_UNIT_FAILED",
	"WRITE_ERROR_AFTER_REMAP",
	"INIT_RESET_RECOVERY_ABORTED"
};

/*
 * All Possible Logical Volume Status
 */

char *log_vol_status[] = {
	"OK",
	"Failed",
	"Not Configured",
	"Regenerating",
	"Needs Rebuild Permission",
	"Rebuilding",
	"Wrong Drive Replaced",
	"Bad Drive Connection",
	"Box Overheating",
	"Box Overheated",
	"Volume Expanding",
	"Not Yet Available",
	"Volume Needs to Expand",
	"Unknown"
};

/*
 * Function	: 	cpqary3_send_NOE_command
 * Description	: 	This routine builds and submits the NOE Command
 *  			to the Controller.
 * Called By	:   	cpqary3_attach(), cpqary3_NOE_handler()
 * Parameters	: 	per-controller, per-command,
 *  			Flag to signify first time or otherwise
 * Calls	:   	cpqary3_alloc_phyctgs_mem(), cpqary3_cmdlist_occupy(),
 *			cpqary3_submit(), cpqary3_add2submitted_cmdq(),
 *			cpqary3_free_phyctgs_mem()
 * Return Values: 	SUCCESS / FAILURE
 *			[Shall fail only if memory allocation issues exist]
 */
uint8_t
cpqary3_send_NOE_command(cpqary3_t *ctlr, cpqary3_cmdpvt_t *memp, uint8_t flag)
{
	uint32_t		phys_addr = 0;
	NoeBuffer 		*databuf;
	CommandList_t		*cmdlist;
	cpqary3_phyctg_t	*phys_handle;
	int			rv;

	/*
	 * NOTE : DO NOT perform this operation for memp. Shall result in a
	 * failure of submission of the NOE command as it shall be NULL for
	 * the very first time
	 */
	RETURN_FAILURE_IF_NULL(ctlr);

	/*
	 * Allocate Memory for Return data
	 * if failure, RETURN.
	 * Allocate Memory for CommandList
	 * If error, RETURN.
	 * get the Request Block from the CommandList
	 * Fill in the Request Packet with the corresponding values
	 * Special Information can be filled in the "bno" field of
	 * the request structure.
	 * Here, the "bno" field is filled for Asynchronous Mode.
	 * Submit the Command.
	 * If Failure, WARN and RETURN.
	 */
	if (CPQARY3_NOE_RESUBMIT == flag) {
		if ((NULL == memp) || (NULL == memp->cmdlist_memaddr)) {
			cmn_err(CE_WARN, " CPQary3 : _send_NOE_command : "
			    "Re-Use Not possible; CommandList NULL");
			return (CPQARY3_FAILURE);
		}

		bzero(MEM2DRVPVT(memp)->sg, sizeof (NoeBuffer));
		memp->cmdlist_memaddr->Header.Tag.drvinfo_n_err =
		    CPQARY3_NOECMD_SUCCESS;
	} else if (CPQARY3_NOE_INIT == flag) {
		phys_handle =
		    (cpqary3_phyctg_t *)MEM_ZALLOC(sizeof (cpqary3_phyctg_t));
		if (!phys_handle)
			return (CPQARY3_FAILURE);

		databuf = (NoeBuffer *)cpqary3_alloc_phyctgs_mem(ctlr,
		    sizeof (NoeBuffer), &phys_addr, phys_handle);
		if (!databuf) {
			return (CPQARY3_FAILURE);
		}
		bzero(databuf, sizeof (NoeBuffer));

		if (NULL == (memp = cpqary3_cmdlist_occupy(ctlr))) {
			cpqary3_free_phyctgs_mem(phys_handle,
			    CPQARY3_FREE_PHYCTG_MEM);
			return (CPQARY3_FAILURE);
		}

		memp->driverdata = (cpqary3_private_t *)
		    MEM_ZALLOC(sizeof (cpqary3_private_t));
		if (NULL == memp->driverdata) {
			cpqary3_free_phyctgs_mem(phys_handle,
			    CPQARY3_FREE_PHYCTG_MEM);
			cpqary3_cmdlist_release(memp, CPQARY3_HOLD_SW_MUTEX);
			return (CPQARY3_FAILURE);
		}
		memp->driverdata->sg = databuf;
		memp->driverdata->phyctgp = phys_handle;

		cmdlist = memp->cmdlist_memaddr;
		cmdlist->Header.SGTotal = 1;
		cmdlist->Header.SGList = 1;
		cmdlist->Header.Tag.drvinfo_n_err = CPQARY3_NOECMD_SUCCESS;
		cmdlist->Header.LUN.PhysDev.Mode = PERIPHERIAL_DEV_ADDR;

		cmdlist->Request.CDBLen = CISS_NOE_CDB_LEN;
		cmdlist->Request.Timeout = 0;
		cmdlist->Request.Type.Type = CISS_TYPE_CMD;
		cmdlist->Request.Type.Attribute = CISS_ATTR_HEADOFQUEUE;
		cmdlist->Request.Type.Direction = CISS_XFER_READ;
		cmdlist->Request.CDB[0] = CISS_NEW_READ;
		cmdlist->Request.CDB[1] = BMIC_NOTIFY_ON_EVENT;
		cmdlist->Request.CDB[10] = (NOE_BUFFER_LENGTH >> 8) & 0xff;
		cmdlist->Request.CDB[11] = NOE_BUFFER_LENGTH & 0xff;

		cmdlist->SG[0].Addr = phys_addr;
		cmdlist->SG[0].Len = NOE_BUFFER_LENGTH;
	}

	/* PERF */

	memp->complete = cpqary3_noe_complete;

	mutex_enter(&ctlr->hw_mutex);
	rv = cpqary3_submit(ctlr, memp->cmdlist_phyaddr);
	mutex_exit(&ctlr->hw_mutex);

	if (rv != 0)
		return (CPQARY3_FAILURE);

	/* PERF */
	return (CPQARY3_SUCCESS);
}

/*
 * Function	: 	cpqary3_disable_NOE_command
 * Description	: 	This routine disables the Event Notifier
 *			for the specified Controller.
 * Called By	: 	cpqary3_cleanup()
 * Parameters	: 	Per Controller Structure
 * Calls	:   	cpqary3_cmdlist_occupy(), cpqary3_submit(),
 *			cpqary3_add2submitted_cmdq()
 * Return Values: 	SUCCESS / FAILURE
 *			[Shall fail only if Memory Constraints exist]
 */
uint8_t
cpqary3_disable_NOE_command(cpqary3_t *ctlr)
{
	CommandList_t		*cmdlist;
	cpqary3_cmdpvt_t	*memp;
	int			rv;

	RETURN_FAILURE_IF_NULL(ctlr);

	/*
	 * Allocate Memory for CommandList
	 * If error, RETURN.
	 * get the Request Block from the CommandList
	 * Fill in the Request Packet with the corresponding values
	 * Submit the Command.
	 * If Failure, WARN and RETURN.
	 */

	if (NULL == (memp = cpqary3_cmdlist_occupy(ctlr))) {
		cmn_err(CE_WARN, "CPQary3 : _disable_NOE_command : Failed");
		return (CPQARY3_FAILURE);
	}

	cmdlist = memp->cmdlist_memaddr;
	cmdlist->Header.Tag.drvinfo_n_err = CPQARY3_NOECMD_SUCCESS;
	cmdlist->Header.LUN.PhysDev.Mode = PERIPHERIAL_DEV_ADDR;

	cmdlist->Request.CDBLen = CISS_CANCEL_NOE_CDB_LEN;
	cmdlist->Request.Timeout = 0;
	cmdlist->Request.Type.Type = CISS_TYPE_CMD;
	cmdlist->Request.Type.Attribute = CISS_ATTR_HEADOFQUEUE;
	cmdlist->Request.Type.Direction = CISS_XFER_NONE;
	cmdlist->Request.CDB[0] = ARRAY_WRITE;	/* 0x27 */
	cmdlist->Request.CDB[6] = BMIC_CANCEL_NOTIFY_ON_EVENT;

	/* PERF */

	memp->complete = cpqary3_noe_complete;

	mutex_enter(&ctlr->hw_mutex);
	rv = cpqary3_submit(ctlr, memp->cmdlist_phyaddr);
	mutex_exit(&ctlr->hw_mutex);

	if (rv != 0)
		return (CPQARY3_FAILURE);

	/* PERF */
	return (CPQARY3_SUCCESS);
}

/*
 * Function	: 	cpqary3_NOE_handler
 * Description	: 	This routine handles all those NOEs tabulated at the
 *			begining of this code.
 * Called By	: 	cpqary3_process_pkt()
 * Parameters	: 	Pointer to the Command List
 * Calls	:   	cpqary3_send_NOE_command(),
 *			cpqary3_display_spare_status()
 *			cpqary3_free_phyctgs_mem(), cpqary3_cmdlist_release()
 * Return Values: 	None
 */
void
cpqary3_NOE_handler(cpqary3_cmdpvt_t *memp)
{
	uint16_t		drive = 0;
	NoeBuffer 		*evt;
	cpqary3_t		*ctlr;
	cpqary3_phyctg_t	*phys_handle;
	uint8_t			driveId = 0;

	/*
	 * This should never happen....
	 * If the pointer passed as argument is NULL, Panic the System.
	 */
	VERIFY(memp != NULL);

	evt = (NoeBuffer *)MEM2DRVPVT(memp)->sg;
	ctlr = (cpqary3_t *)memp->ctlr;
	phys_handle = (cpqary3_phyctg_t *)MEM2DRVPVT(memp)->phyctgp;

	/* Don't display more than 79 characters */
	evt->ascii_message[79] = 0;


	switch (evt->event_class_code) {
	case CLASS_PROTOCOL:
		/*
		 * the following cases are not handled:
		 * 000 	: This is for Synchronous NOE.
		 *	  CPQary3 follows asynchronous NOE.
		 * 002	: Asynchronous NOE time out.
		 *	  CPQary3 does not implement time
		 *	  outs for NOE. It shall always reside in the HBA.
		 */

		cmn_err(CE_NOTE, " %s", ctlr->hba_name);
		if ((evt->event_subclass_code == SUB_CLASS_NON_EVENT) &&
		    (evt->event_detail_code == DETAIL_DISABLED)) {
			cmn_err(CE_CONT, " %s", ctlr->hba_name);
			cmn_err(CE_CONT,
			    "CPQary3 : Event Notifier Disabled \n");
			MEM_SFREE(memp->driverdata, sizeof (cpqary3_private_t));
			cpqary3_free_phyctgs_mem(phys_handle,
			    CPQARY3_FREE_PHYCTG_MEM);
			cpqary3_cmdlist_release(memp, CPQARY3_NO_MUTEX);
			return;
		} else if ((evt->event_subclass_code ==
		    SUB_CLASS_PROTOCOL_ERR) &&
		    (evt->event_detail_code == DETAIL_EVENT_Q_OVERFLOW)) {
			cmn_err(CE_CONT, " %s\n", evt->ascii_message);
		}
		cmn_err(CE_CONT, "\n");
		break;

	case CLASS_HOT_PLUG:
		if (evt->event_subclass_code == SUB_CLASS_HP_CHANGE) {
			cmn_err(CE_NOTE, " %s", ctlr->hba_name);
			cmn_err(CE_CONT, " %s\n", evt->ascii_message);

			/*
			 * Fix for QUIX 1000440284: Display the Physical
			 * Drive Num info only for CISS Controllers
			 */

			if (!(ctlr->bddef->bd_flags & SA_BD_SAS)) {
				driveId =
				    /* LINTED: alignment */
				    *(uint16_t *)(&evt->event_specific_data[0]);
				if (driveId & 0x80) {
					driveId -= 0x80;
					cmn_err(CE_CONT, " Physical Drive Num "
					    "....... SCSI Port %u, "
					    "Drive Id %u\n",
					    (driveId / 16) + 1,
					    (driveId % 16));
				} else {
					cmn_err(CE_CONT, " Physical Drive Num "
					    "....... SCSI Port %u, "
					    "Drive Id %u\n",
					    (driveId / 16) + 1, (driveId % 16));
				}
			}

			cmn_err(CE_CONT, " Configured Drive ? ....... %s\n",
			    evt->event_specific_data[2] ? "YES" : "NO");
			if (evt->event_specific_data[3]) {
				cmn_err(CE_CONT, " Spare Drive? "
				    "............. %s\n",
				    evt->event_specific_data[3] ? "YES" : "NO");
			}
		} else if (evt->event_subclass_code == SUB_CLASS_SB_HP_CHANGE) {
			if (evt->event_detail_code == DETAIL_PATH_REMOVED) {
				cmn_err(CE_WARN, " %s", ctlr->hba_name);
				cmn_err(CE_CONT,
				    " Storage Enclosure cable or %s\n",
				    evt->ascii_message);
			} else if (evt->event_detail_code ==
			    DETAIL_PATH_REPAIRED) {
				cmn_err(CE_NOTE, " %s", ctlr->hba_name);
				cmn_err(CE_CONT,
				    " Storage Enclosure Cable or %s\n",
				    evt->ascii_message);
			} else {
				cmn_err(CE_NOTE, " %s", ctlr->hba_name);
				cmn_err(CE_CONT, " %s\n", evt->ascii_message);
			}
		} else {
			cmn_err(CE_NOTE, " %s", ctlr->hba_name);
			cmn_err(CE_CONT, " %s\n", evt->ascii_message);
		}

		cmn_err(CE_CONT, "\n");
		break;

	case CLASS_HARDWARE:
	case CLASS_ENVIRONMENT:
		cmn_err(CE_NOTE, " %s", ctlr->hba_name);
		cmn_err(CE_CONT, " %s\n", evt->ascii_message);
		cmn_err(CE_CONT, "\n");
		break;

	case CLASS_PHYSICAL_DRIVE:
		cmn_err(CE_WARN, " %s", ctlr->hba_name);
		cmn_err(CE_CONT, " %s\n", evt->ascii_message);

		/*
		 * Fix for QUIX 1000440284: Display the Physical Drive
		 * Num info only for CISS Controllers
		 */

		if (!(ctlr->bddef->bd_flags & SA_BD_SAS)) {
			/* LINTED: alignment */
			driveId = *(uint16_t *)(&evt->event_specific_data[0]);
			if (driveId & 0x80) {
				driveId -= 0x80;
				cmn_err(CE_CONT, " Physical Drive Num ....... "
				    "SCSI Port %u, Drive Id %u\n",
				    (driveId / 16) + 1, (driveId % 16));
			} else {
				cmn_err(CE_CONT, " Physical Drive Num ....... "
				    "SCSI Port %u, Drive Id %u\n",
				    (driveId / 16) + 1, (driveId % 16));
			}
		}

		if (evt->event_specific_data[2] < MAX_KNOWN_FAILURE_REASON) {
			cmn_err(CE_CONT, " Failure Reason............ %s\n",
			    ascii_failure_reason[evt->event_specific_data[2]]);
		} else {
			cmn_err(CE_CONT,
			    " Failure Reason............ UNKNOWN \n");
		}

		cmn_err(CE_CONT, "\n");
		break;

	case CLASS_LOGICAL_DRIVE:
		cmn_err(CE_NOTE, " %s", ctlr->hba_name);

		/*
		 * Fix for QXCR1000717274 - We are appending the logical
		 * voulme number by one to be in sync with logical volume
		 * details given by HPQacucli
		 */

		if ((evt->event_subclass_code == SUB_CLASS_STATUS) &&
		    (evt->event_detail_code == DETAIL_CHANGE)) {
			cmn_err(CE_CONT, " State change, logical drive %u\n",
			    /* LINTED: alignment */
			    (*(uint16_t *)(&evt->event_specific_data[0]) + 1));
			cmn_err(CE_CONT, " New Logical Drive State... %s\n",
			    log_vol_status[evt->event_specific_data[3]]);

			/*
			 * If the Logical drive has FAILED or it was
			 * NOT CONFIGURED, in the corresponding target
			 * structure, set flag as NONE to suggest that no
			 * target exists at this id.
			 */

			if ((evt->event_specific_data[3] == 1) ||
			    (evt->event_specific_data[3] == 2)) {
				/* LINTED: alignment */
				drive =	*(uint16_t *)
				    (&evt->event_specific_data[0]);
				drive = ((drive < CTLR_SCSI_ID)
				    ? drive : drive + CPQARY3_TGT_ALIGNMENT);
				if (ctlr && ctlr->cpqary3_tgtp[drive]) {
					ctlr->cpqary3_tgtp[drive]->type =
					    CPQARY3_TARGET_NONE;
				}
			}

			if (evt->event_specific_data[4] & SPARE_REBUILDING) {
				cmn_err(CE_CONT, " Logical Drive %d: "
				    "Data is rebuilding on spare drive\n",
				    /* LINTED: alignment */
				    (*(uint16_t *)
				    (&evt->event_specific_data[0]) + 1));
			}

			if (evt->event_specific_data[4] & SPARE_REBUILT) {
				cmn_err(CE_CONT,
				    " Logical Drive %d: Rebuild complete. "
				    "Spare is now active\n",
				    /* LINTED: alignment */
				    (*(uint16_t *)
				    (&evt->event_specific_data[0]) + 1));
			}
		} else if ((evt->event_subclass_code == SUB_CLASS_STATUS) &&
		    (evt->event_detail_code == MEDIA_EXCHANGE)) {
			cmn_err(CE_CONT, " Media exchange detected, "
			    "logical drive %u\n",
			    /* LINTED: alignment */
			    (*(uint16_t *)
			    (&evt->event_specific_data[0]) + 1));
		} else {
			cmn_err(CE_CONT, " %s\n", evt->ascii_message);
		}

		cmn_err(CE_CONT, "\n");
		break;

	default:
		cmn_err(CE_NOTE, "%s", ctlr->hba_name);
		cmn_err(CE_CONT, " %s\n", evt->ascii_message);
		cmn_err(CE_CONT, "\n");
		break;
	}

	/*
	 * Here, we reuse this command block to resubmit the NOE
	 * command.
	 * Ideally speaking, the resubmit should never fail
	 */
	if (CPQARY3_FAILURE ==
	    cpqary3_send_NOE_command(ctlr, memp, CPQARY3_NOE_RESUBMIT)) {
		cmn_err(CE_WARN, "CPQary3: Failed to ReInitialize "
		    "NOTIFY OF EVENT");
		cpqary3_free_phyctgs_mem(MEM2DRVPVT(memp)->phyctgp,
		    CPQARY3_FREE_PHYCTG_MEM);
		cpqary3_cmdlist_release(memp, CPQARY3_NO_MUTEX);
	}
}

/* PERF */
/*
 * Function	:      	cpqary3_noe_complete
 * Description	:      	This routine processes the completed
 *			NOE commands and
 *			initiates any callback that is needed.
 * Called By	:      	cpqary3_send_NOE_command,
 *			cpqary3_disable_NOE_command
 * Parameters	:      	per-command
 * Calls	:      	cpqary3_NOE_handler, cpqary3_cmdlist_release
 * Return Values:      	None
 */
void
cpqary3_noe_complete(cpqary3_cmdpvt_t *cpqary3_cmdpvtp)
{
	ASSERT(cpqary3_cmdpvtp != NULL);

	if (CPQARY3_TIMEOUT == cpqary3_cmdpvtp->cmdpvt_flag) {
		cpqary3_cmdlist_release(cpqary3_cmdpvtp, CPQARY3_NO_MUTEX);
		return;
	}

	if (cpqary3_cmdpvtp->cmdlist_memaddr->Request.CDB[6] ==
	    BMIC_CANCEL_NOTIFY_ON_EVENT) {
		cv_signal(&cpqary3_cmdpvtp->ctlr->cv_noe_wait);
		cpqary3_cmdlist_release(cpqary3_cmdpvtp, CPQARY3_NO_MUTEX);
	} else {
		cpqary3_NOE_handler(cpqary3_cmdpvtp);
	}
}

/* PERF */