/*
* 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 */