root/usr/src/uts/common/io/fibre-channel/fca/emlxs/emlxs_ip.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at
 * http://www.opensource.org/licenses/cddl1.txt.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2004-2011 Emulex. All rights reserved.
 * Use is subject to license terms.
 */

#include <emlxs.h>

/* Required for EMLXS_CONTEXT in EMLXS_MSGF calls */
EMLXS_MSG_DEF(EMLXS_IP_C);


extern int32_t
emlxs_ip_handle_event(emlxs_hba_t *hba, CHANNEL *cp, IOCBQ *iocbq)
{
        emlxs_port_t *port = &PPORT;
        IOCB *cmd;
        emlxs_buf_t *sbp;
        NODELIST *ndlp;

        cmd = &iocbq->iocb;

        HBASTATS.IpEvent++;

        sbp = (emlxs_buf_t *)iocbq->sbp;

        if (!sbp) {
                HBASTATS.IpStray++;

                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_stray_ip_completion_msg,
                    "cmd=0x%x iotag=0x%x status=0x%x perr=0x%x",
                    (uint32_t)cmd->ULPCOMMAND, (uint32_t)cmd->ULPIOTAG,
                    cmd->ULPSTATUS, cmd->un.ulpWord[4]);

                return (EIO);
        }

        if (cp->channelno != hba->channel_ip) {
                HBASTATS.IpStray++;

                return (0);
        }

        port = sbp->iocbq.port;

        switch (cmd->ULPCOMMAND) {
                /*
                 * Error: Abnormal BCAST command completion  (Local error)
                 */
        case CMD_XMIT_BCAST_CN:
        case CMD_XMIT_BCAST64_CN:

                HBASTATS.IpBcastCompleted++;
                HBASTATS.IpBcastError++;

                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
                    "XMIT BCAST completion error cmd=0x%x status=0x%x "
                    "[%08x,%08x]", cmd->ULPCOMMAND, cmd->ULPSTATUS,
                    cmd->un.ulpWord[4], cmd->un.ulpWord[5]);

                emlxs_pkt_complete(sbp, cmd->ULPSTATUS,
                    cmd->un.grsp.perr.statLocalError, 1);

                break;

                /*
                 * Error: Abnormal XMIT SEQUENCE command completion
                 * (Local error)
                 */
        case CMD_XMIT_SEQUENCE_CR:
        case CMD_XMIT_SEQUENCE64_CR:

                HBASTATS.IpSeqCompleted++;
                HBASTATS.IpSeqError++;

                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
                    "XMIT SEQUENCE CR completion error: cmd=%x status=0x%x "
                    "[%08x,%08x]", cmd->ULPCOMMAND, cmd->ULPSTATUS,
                    cmd->un.ulpWord[4], cmd->un.ulpWord[5]);

                emlxs_pkt_complete(sbp, cmd->ULPSTATUS,
                    cmd->un.grsp.perr.statLocalError, 1);

                break;

                /*
                 * Normal BCAST completion
                 */
        case CMD_XMIT_BCAST_CX:
        case CMD_XMIT_BCAST64_CX:

                HBASTATS.IpBcastCompleted++;
                HBASTATS.IpBcastGood++;

                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
                    "XMIT BCAST CN completion: cmd=%x status=0x%x [%08x,%08x]",
                    cmd->ULPCOMMAND, cmd->ULPSTATUS, cmd->un.ulpWord[4],
                    cmd->un.ulpWord[5]);

                emlxs_pkt_complete(sbp, cmd->ULPSTATUS,
                    cmd->un.grsp.perr.statLocalError, 1);

                break;

                /*
                 * Normal XMIT SEQUENCE completion
                 */
        case CMD_XMIT_SEQUENCE_CX:
        case CMD_XMIT_SEQUENCE64_CX:

                HBASTATS.IpSeqCompleted++;

                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
                    "XMIT SEQUENCE CR completion: cmd=%x status=0x%x"
                    "[%08x,%08x]", cmd->ULPCOMMAND, cmd->ULPSTATUS,
                    cmd->un.ulpWord[4], cmd->un.ulpWord[5]);

                if (cmd->ULPSTATUS) {
                        HBASTATS.IpSeqError++;

                        if ((cmd->ULPSTATUS == IOSTAT_LOCAL_REJECT) &&
                            ((cmd->un.ulpWord[4] & 0xff) == IOERR_NO_XRI)) {
                                ndlp = (NODELIST *)sbp->node;
                                if ((cmd->ULPCONTEXT == ndlp->nlp_Xri) &&
                                    !(ndlp->nlp_flag[hba->channel_ip] &
                                    NLP_RPI_XRI)) {
                                        ndlp->nlp_Xri = 0;
                                        (void) emlxs_create_xri(port, cp, ndlp);
                                }
                        }
                } else {
                        HBASTATS.IpSeqGood++;
                }

                emlxs_pkt_complete(sbp, cmd->ULPSTATUS,
                    cmd->un.grsp.perr.statLocalError, 1);

                break;

        default:

                HBASTATS.IpStray++;

                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_invalid_ip_msg,
                    "Invalid iocb: cmd=0x%x", cmd->ULPCOMMAND);

                break;

        }       /* switch(cmd->ULPCOMMAND) */


        return (0);

} /* emlxs_ip_handle_event() */


extern int32_t
emlxs_ip_handle_unsol_req(emlxs_port_t *port, CHANNEL *cp, IOCBQ *iocbq,
    MATCHMAP *mp, uint32_t size)
{
        emlxs_hba_t *hba = HBA;
        fc_unsol_buf_t *ubp;
        IOCB *cmd;
        NETHDR *nd;
        NODELIST *ndlp;
        uint8_t *mac;
        emlxs_ub_priv_t *ub_priv;
        uint32_t sid;
        uint32_t i;
        uint32_t IpDropped = 1;
        uint32_t IpBcastReceived = 0;
        uint32_t IpSeqReceived = 0;

        cmd = &iocbq->iocb;
        ubp = NULL;

        for (i = 0; i < MAX_VPORTS; i++) {
                port = &VPORT(i);

                if (!(port->flag & EMLXS_INI_BOUND) ||
                    !(port->flag & EMLXS_PORT_IP_UP)) {
                        continue;
                }

                ubp =
                    (fc_unsol_buf_t *)emlxs_ub_get(port, size,
                    FC_TYPE_IS8802_SNAP, 0);

                if (!ubp) {
                        /* Theoretically we should never get here. */
                        /* There should be one DMA buffer for every ub */
                        /* buffer. If we are out of ub buffers */
                        /* then some how this matching has been corrupted */

                        EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_unsol_ip_dropped_msg,
                            "Buffer not found. paddr=%lx",
                            PADDR(cmd->un.cont64[0].addrHigh,
                            cmd->un.cont64[0].addrLow));

                        continue;
                }

                bcopy(mp->virt, ubp->ub_buffer, size);

                ub_priv = ubp->ub_fca_private;
                nd = (NETHDR *)ubp->ub_buffer;
                mac = nd->fc_srcname.IEEE;
                ndlp = emlxs_node_find_mac(port, mac);

                if (ndlp) {
                        sid = ndlp->nlp_DID;

                        if ((ndlp->nlp_Xri == 0) &&
                            !(ndlp->nlp_flag[hba->channel_ip] & NLP_RPI_XRI)) {
                                (void) emlxs_create_xri(port, cp, ndlp);
                        }
                }

                /*
                 * If no node is found, then check if this is a
                 * broadcast frame
                 */
                else if (cmd->un.xrseq.w5.hcsw.Fctl & BC) {
                        sid = cmd->un.ulpWord[4] & 0x00ffffff;
                }

                else {
                        /* We have to drop this frame because we do not have */
                        /* the S_ID of the request */
                        EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_unsol_ip_dropped_msg,
                            "Node not found. mac=%02x%02x%02x%02x%02x%02x",
                            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

                        (void) emlxs_fca_ub_release((opaque_t)port, 1,
                            &ubp->ub_token);

                        continue;
                }

                if (cmd->un.xrseq.w5.hcsw.Fctl & BC) {
                        IpBcastReceived++;
                } else {
                        IpSeqReceived++;
                }

                /*
                 * Setup frame header
                 */
                ubp->ub_frame.r_ctl = cmd->un.xrseq.w5.hcsw.Rctl;
                ubp->ub_frame.type = cmd->un.xrseq.w5.hcsw.Type;
                ubp->ub_frame.s_id = sid;
                ubp->ub_frame.ox_id = ub_priv->token;
                ubp->ub_frame.rx_id = cmd->ULPCONTEXT;
                ubp->ub_class = FC_TRAN_CLASS3;

                emlxs_ub_callback(port, ubp);
                IpDropped = 0;
        }
        port = &PPORT;

        if (IpDropped) {
                HBASTATS.IpDropped++;
        }

        if (IpBcastReceived) {
                HBASTATS.IpBcastReceived++;
        }

        if (IpSeqReceived) {
                HBASTATS.IpSeqReceived++;
        }

        return (0);

} /* emlxs_ip_handle_unsol_req() */


extern int32_t
emlxs_ip_handle_rcv_seq_list(emlxs_hba_t *hba, CHANNEL *cp, IOCBQ *iocbq)
{
        emlxs_port_t *port = &PPORT;
        IOCB *cmd;
        uint64_t bdeAddr;
        MATCHMAP *mp = NULL;
        HBQE_t *hbqE;
        uint32_t hbq_id;
        uint32_t hbqe_tag;
        RING *rp;

        /*
         * No action required for now.
         */
        cmd = &iocbq->iocb;
        rp = &hba->sli.sli3.ring[cp->channelno];

        HBASTATS.IpRcvEvent++;

        EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
            "Receive sequence list: cmd=0x%x iotag=0x%x status=0x%x "
            "w4=0x%x channelno=0x%x", cmd->ULPCOMMAND, cmd->ULPIOTAG,
            cmd->ULPSTATUS, cmd->un.ulpWord[4], cp->channelno);

        if (cmd->ULPSTATUS) {
                goto out;
        }

        hbqE = (HBQE_t *)&iocbq->iocb;
        hbq_id = hbqE->unt.ext.HBQ_tag;
        hbqe_tag = hbqE->unt.ext.HBQE_tag;

        if (hba->flag & FC_HBQ_ENABLED) {
                HBQ_INIT_t *hbq;

                hbq = &hba->sli.sli3.hbq_table[hbq_id];

                HBASTATS.IpUbPosted--;

                if (hbqe_tag >= hbq->HBQ_numEntries) {
                        mp = NULL;
                } else {
                        mp = hba->sli.sli3.hbq_table
                            [hbq_id].HBQ_PostBufs[hbqe_tag];
                }
        } else {
                /* Check for valid buffer */
                if (!(cmd->un.cont64[0].tus.f.bdeFlags & BUFF_TYPE_INVALID)) {
                        bdeAddr =
                            PADDR(cmd->un.cont64[0].addrHigh,
                            cmd->un.cont64[0].addrLow);
                        mp = emlxs_mem_get_vaddr(hba, rp, bdeAddr);
                }
        }

out:

        if (hba->flag & FC_HBQ_ENABLED) {
                emlxs_update_HBQ_index(hba, hbq_id);
        } else {
                if (mp) {
                        emlxs_mem_put(hba, MEM_IPBUF, (void *)mp);
                }
                (void) emlxs_post_buffer(hba, rp, 1);
        }

        HBASTATS.IpDropped++;

        return (0);

} /* emlxs_ip_handle_rcv_seq_list() */



/*
 * Process a create_xri command completion.
 */
extern int32_t
emlxs_handle_create_xri(emlxs_hba_t *hba, CHANNEL *cp, IOCBQ *iocbq)
{
        emlxs_port_t *port = &PPORT;
        IOCB *cmd;
        NODELIST *ndlp;
        fc_packet_t *pkt;
        emlxs_buf_t *sbp;
        int32_t rval = 0;

        cmd = &iocbq->iocb;

        sbp = (emlxs_buf_t *)iocbq->sbp;

        if (!sbp) {
                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_stray_ip_completion_msg,
                    "create_xri: cmd=0x%x iotag=0x%x status=0x%x w4=0x%x. "
                    "NULL sbp found.",
                    cmd->ULPCOMMAND, cmd->ULPIOTAG, cmd->ULPSTATUS,
                    cmd->un.ulpWord[4]);

                return (EIO);
        }

        /* check for first xmit completion in sequence */
        ndlp = (NODELIST *)sbp->node;

        if (!ndlp) {
                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_bad_ip_completion_msg,
                    "create_xri: cmd=0x%x iotag=0x%x status=0x%x w4=0x%x. "
                    "NULL node found.",
                    cmd->ULPCOMMAND, cmd->ULPIOTAG, cmd->ULPSTATUS,
                    cmd->un.ulpWord[4]);

                rval = EIO;
                goto done;
        }

        if (cmd->ULPSTATUS) {
                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_bad_ip_completion_msg,
                    "create_xri: cmd=0x%x iotag=0x%x status=0x%x w4=0x%x. "
                    "Completion error.",
                    cmd->ULPCOMMAND, cmd->ULPIOTAG, cmd->ULPSTATUS,
                    cmd->un.ulpWord[4]);

                mutex_enter(&EMLXS_TX_CHANNEL_LOCK);
                ndlp->nlp_flag[cp->channelno] &= ~NLP_RPI_XRI;
                mutex_exit(&EMLXS_TX_CHANNEL_LOCK);

                rval = EIO;
                goto done;
        }

        mutex_enter(&EMLXS_TX_CHANNEL_LOCK);
        ndlp->nlp_Xri = cmd->ULPCONTEXT;
        ndlp->nlp_flag[cp->channelno] &= ~NLP_RPI_XRI;
        mutex_exit(&EMLXS_TX_CHANNEL_LOCK);

        EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
            "create_xri completed: DID=0x%x Xri=0x%x iotag=0x%x",
            ndlp->nlp_DID, ndlp->nlp_Xri, cmd->ULPIOTAG);

done:
        pkt = sbp->pkt;
        if (pkt) {
                emlxs_pkt_free(pkt);
        }

        return (rval);

} /* emlxs_handle_create_xri()  */


/*
 * Issue an iocb command to create an exchange with the remote Nport
 * specified by the NODELIST entry.
 */
extern int32_t
emlxs_create_xri(emlxs_port_t *port, CHANNEL *cp, NODELIST *ndlp)
{
        emlxs_hba_t *hba = HBA;
        IOCB *icmd;
        IOCBQ *iocbq;
        fc_packet_t *pkt;
        emlxs_buf_t *sbp;
        uint16_t iotag;

        /* Check if an XRI has already been requested */
        mutex_enter(&EMLXS_TX_CHANNEL_LOCK);
        if (ndlp->nlp_Xri != 0 ||
            (ndlp->nlp_flag[cp->channelno] & NLP_RPI_XRI)) {
                mutex_exit(&EMLXS_TX_CHANNEL_LOCK);
                return (0);
        }
        ndlp->nlp_flag[cp->channelno] |= NLP_RPI_XRI;
        mutex_exit(&EMLXS_TX_CHANNEL_LOCK);

        if (!(pkt = emlxs_pkt_alloc(port, 0, 0, 0, KM_NOSLEEP))) {
                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
                    "create_xri failed: Unable to allocate pkt. did=0x%x",
                    ndlp->nlp_DID);

                goto fail;
        }

        sbp = (emlxs_buf_t *)pkt->pkt_fca_private;
        iocbq = &sbp->iocbq;

        /* Clear the PACKET_ULP_OWNED flag */
        sbp->pkt_flags &= ~PACKET_ULP_OWNED;

        /* Get the iotag by registering the packet */
        iotag = emlxs_register_pkt(cp, sbp);

        if (!iotag) {
                /*
                 * No more command slots available, retry later
                 */
                emlxs_pkt_free(pkt);

                EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
                    "create_xri failed: Unable to allocate IOTAG. did=0x%x",
                    ndlp->nlp_DID);

                goto fail;
        }

        icmd = &iocbq->iocb;
        icmd->ULPIOTAG = iotag;
        icmd->ULPCONTEXT = ndlp->nlp_Rpi;
        icmd->ULPLE = 1;
        icmd->ULPCOMMAND = CMD_CREATE_XRI_CR;
        icmd->ULPOWNER = OWN_CHIP;

        /* Initalize iocbq */
        iocbq->port = (void *)port;
        iocbq->node = (void *)ndlp;
        iocbq->channel = (void *)cp;

        mutex_enter(&sbp->mtx);
        sbp->node = (void *)ndlp;
        sbp->channel = cp;
        mutex_exit(&sbp->mtx);

        EMLXS_MSGF(EMLXS_CONTEXT, &emlxs_ip_detail_msg,
            "create_xri sent: DID=0x%x Xri=0x%x iotag=0x%x", ndlp->nlp_DID,
            ndlp->nlp_Xri, iotag);

        EMLXS_SLI_ISSUE_IOCB_CMD(hba, cp, iocbq);

        return (0);

fail:

        /* Clear the XRI flag */
        mutex_enter(&EMLXS_TX_CHANNEL_LOCK);
        ndlp->nlp_flag[cp->channelno] &= ~NLP_RPI_XRI;
        mutex_exit(&EMLXS_TX_CHANNEL_LOCK);

        return (1);

} /* emlxs_create_xri() */