/*
 * 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 usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * 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 2014 QLogic Corporation
 * The contents of this file are subject to the terms of the
 * QLogic End User 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.qlogic.com/Resources/Documents/DriverDownloadHelp/
 * QLogic_End_User_Software_License.txt
 * See the License for the specific language governing permissions
 * and limitations under the License.
 */

/*
 * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
 */

#include "bnxe.h"

int BnxeRouteTxRing(um_device_t * pUM,
                    mblk_t *      pMblk)
{
    u32_t     numRings = pUM->devParams.numRings;
    int       ring = 0;
    uint8_t * pHdr;
    mblk_t *  pTmpMblk;
    size_t    mblkLen;
    ushort_t  etype;
    size_t    eHdrSize;

    if (!numRings)
    {
        return 0;
    }

    /*
     * Need enough space to cover the ethernet header (+vlan), max ip header,
     * and the first 4 bytes of the TCP/IP header (src/dst ports).
     */
    size_t  hdrs_size;
    uint8_t hdrs_buf[sizeof(struct ether_vlan_header) +
                     IP_MAX_HDR_LENGTH +
                     sizeof(uint32_t)];

    switch (pUM->devParams.routeTxRingPolicy)
    {
    case BNXE_ROUTE_RING_TCPUDP:

        pHdr = pMblk->b_rptr;

        etype = ntohs(((struct ether_header *)pHdr)->ether_type);

        if (etype == ETHERTYPE_VLAN)
        {
            etype    = ntohs(((struct ether_vlan_header *)pHdr)->ether_type);
            eHdrSize = sizeof(struct ether_vlan_header);
        }
        else
        {
            eHdrSize = sizeof(struct ether_header);
        }

        if (etype == ETHERTYPE_IP)
        {
            mblkLen = MBLKL(pMblk);
            pHdr    = NULL;

            if (mblkLen > (eHdrSize + sizeof(uint8_t)))
            {
                pHdr     = (pMblk->b_rptr + eHdrSize);
                mblkLen -= eHdrSize;

                pHdr = (mblkLen > (((*pHdr & 0x0f) << 2) + sizeof(uint32_t))) ?
                           pMblk->b_rptr : NULL;
            }

            if (pHdr == NULL)
            {
                /* copy the header so it's contiguous in the local hdrs_buf */
                pTmpMblk  = pMblk;
                hdrs_size = 0;

                while (pTmpMblk && (hdrs_size < sizeof(hdrs_buf)))
                {
                    mblkLen = MBLKL(pTmpMblk);

                    if (mblkLen >= (sizeof(hdrs_buf) - hdrs_size))
                    {
                        mblkLen = (sizeof(hdrs_buf) - hdrs_size);
                    }

                    bcopy(pTmpMblk->b_rptr, &hdrs_buf[hdrs_size], mblkLen);

                    hdrs_size += mblkLen;
                    pTmpMblk   = pTmpMblk->b_cont;
                }

                pHdr = hdrs_buf;
            }

            pHdr += eHdrSize;

            if (!(pHdr[6] & 0x3f) && !(pHdr[7] & 0xff))
            {
                switch (pHdr[9])
                {
                case IPPROTO_TCP:
                case IPPROTO_UDP:
                case IPPROTO_ESP:

                    /* source and destination ports */
                    pHdr += (((*pHdr) & 0x0f) << 2);
                    ring  = ((u32_t)(pHdr[0] ^ pHdr[1] ^ pHdr[2] ^ pHdr[3]) %
                             numRings);
                    break;

                case IPPROTO_AH:

                    /* security parameters index */
                    pHdr += (((*pHdr) & 0x0f) << 2);
                    ring  = ((pHdr[4] ^ pHdr[5] ^ pHdr[6] ^ pHdr[7]) %
                             numRings);
                    break;

                default:

                    /* last byte of the destination IP address */
                    ring = (pHdr[19] % numRings);
                    break;
                }
            }
            else
            {
                /* fragmented packet */
                ring = (pHdr[19] % numRings);
            }
        }
        else
        {
            ring = (pMblk->b_band % numRings);
        }

        break;

    case BNXE_ROUTE_RING_DEST_MAC:

        /* last byte of dst mac addr */
        pHdr = pMblk->b_rptr;
        ring = (pHdr[5] % numRings);
        break;

    case BNXE_ROUTE_RING_MSG_PRIO:

        ring = (pMblk->b_band % numRings);
        break;

    case BNXE_ROUTE_RING_NONE:
    default:

        ring = 0;
        break;
    }

    return ring;
}