root/usr/src/uts/common/io/cpqary3/cpqary3_noe.c
/*
 * 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 */