root/usr/src/uts/common/io/bnx/570x/driver/common/lmdev/bnx_lm_send.c
/*
 * Copyright 2014-2017 Cavium, Inc.
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License, v.1,  (the "License").
 *
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License at available
 * at http://opensource.org/licenses/CDDL-1.0
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "lm5706.h"


#ifndef LM_NON_LEGACY_MODE_SUPPORT
/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_status_t
lm_send_packet(
    lm_device_t *pdev,
    u32_t chain_idx,
    lm_packet_t *packet,
    lm_frag_list_t *frags)
{
    u16_t lso_bd_reserved;
    u16_t ipv6_ext_len;
    lm_tx_chain_t *txq;
    tx_bd_t *start_bd;
    tx_bd_t *last_bd;
    tx_bd_t *prod_bd;
    lm_frag_t *frag;
    u16_t prod_idx;
    u32_t flags;
    u32_t cnt;

    txq = &pdev->tx_info.chain[chain_idx];

    if(packet == NULL)
    {
        // hardcode offset in case of L2_ONLY (e.g Solaris)
        u32_t cmd_offset = 34*sizeof(u32_t); //  == OFFSETOF(l4_context_t, l4ctx_cmd)
        MBQ_WR16(
            pdev,
            GET_CID(txq->cid_addr),
            cmd_offset +
                OFFSETOF(tcp_context_cmd_cell_te_t, ccell_tx_host_bidx),
            txq->prod_idx);
        MBQ_WR32(
            pdev,
            GET_CID(txq->cid_addr),
            cmd_offset +
                OFFSETOF(tcp_context_cmd_cell_te_t, ccell_tx_host_bseq),
            txq->prod_bseq);

        return LM_STATUS_SUCCESS;
    }

    #if DBG
    if(frags->cnt == 0)
    {
        DbgBreakMsg("zero frag_cnt\n");

        return LM_STATUS_INVALID_PARAMETER;
    }

    packet->u1.tx.dbg_start_bd = txq->prod_bd;
    packet->u1.tx.dbg_start_bd_idx = txq->prod_idx;
    packet->u1.tx.dbg_frag_cnt = (u16_t) frags->cnt;
    #endif

    last_bd = NULL;

    if(frags->cnt > txq->bd_left)
    {
        /* The caller should have done this check before calling this
         * routine. */
        DbgBreakMsg("No tx bd left.\n");

        return LM_STATUS_RESOURCE;
    }

    txq->bd_left -= (u16_t) frags->cnt;

    packet->size = 0;
    flags = 0;

    if(packet->u1.tx.flags & LM_TX_FLAG_INSERT_VLAN_TAG)
    {
        flags |= TX_BD_FLAGS_VLAN_TAG;
    }

    if((packet->u1.tx.flags & LM_TX_FLAG_TCP_LSO_FRAME) == 0)
    {
        if(packet->u1.tx.flags & LM_TX_FLAG_COMPUTE_IP_CKSUM)
        {
            flags |= TX_BD_FLAGS_IP_CKSUM;
            LM_INC64(&pdev->tx_info.stats.ip_cso_frames, 1);
        }

        if(packet->u1.tx.flags & LM_TX_FLAG_COMPUTE_TCP_UDP_CKSUM)
        {
            flags |= TX_BD_FLAGS_TCP_UDP_CKSUM;
            if(packet->u1.tx.flags & LM_TX_FLAG_IPV6_PACKET)
            {
                LM_INC64(&pdev->tx_info.stats.ipv6_tcp_udp_cso_frames, 1);
            }
            else
            {
                LM_INC64(&pdev->tx_info.stats.ipv4_tcp_udp_cso_frames, 1);
            }
        }
    }

    if(packet->u1.tx.flags & LM_TX_FLAG_DONT_COMPUTE_CRC)
    {
        flags |= TX_BD_FLAGS_DONT_GEN_CRC;
    }

    if(packet->u1.tx.flags & LM_TX_FLAG_TCP_LSO_FRAME)
    {
        if(packet->u1.tx.flags & LM_TX_FLAG_IPV6_PACKET)
        {
            /* TCP option length - bottom 4 bits of TX_BD_FLAGS_SW_OPTION_WORD
             *    in term of the number of 4-byte words.
             * IP header length - bits 1-2 of bd flag, the upper 2 bits of
             *    tx_bd_reserved, and the upper 1 bit of
             *    TX_BD_FLAGS_SW_OPTION_WORD will be used for IPV6 extension
             *    header length in term of 8-btye words.
             * TX_BD_FLAGS_SW_FLAGS bit will be used to indicate IPV6 LSO. */
            flags |= TX_BD_FLAGS_SW_FLAGS;

            if(packet->u1.tx.flags & LM_TX_FLAG_TCP_LSO_SNAP_FRAME)
            {
                flags |= TX_BD_FLAGS_SW_SNAP;
            }

            DbgBreakIf(packet->u1.tx.lso_tcp_hdr_len < 20 ||
                       packet->u1.tx.lso_tcp_hdr_len > 84 ||
                       packet->u1.tx.lso_tcp_hdr_len % 4);

            /* tcp option length in term of number of 32-bit word.  4 bits
             * are used for the number of words. */
            flags |= (packet->u1.tx.lso_tcp_hdr_len - 20) << 6;

            DbgBreakIf(packet->u1.tx.lso_ip_hdr_len < 20 ||
                       packet->u1.tx.lso_ip_hdr_len > 296 ||
                      (packet->u1.tx.lso_ip_hdr_len - 40) % 8);

            /* ipv6 extension header length.  6 bits are used for the number
             * of 64-bit words. */
            ipv6_ext_len = packet->u1.tx.lso_ip_hdr_len - 40;

            DbgBreakIf(ipv6_ext_len & 0x7);

            /* ext_len in number of 8-byte words. */
            ipv6_ext_len >>= 3;

            flags |= (ipv6_ext_len & 0x3) << 1;             /* bit 1-0 */

            lso_bd_reserved = packet->u1.tx.lso_mss;
            lso_bd_reserved |= (ipv6_ext_len & 0xc) << 12;  /* bit 3-2 */

            flags |= (ipv6_ext_len & 0x10) << 8;            /* bit 4  */

            DbgBreakIf(ipv6_ext_len >> 5);  /* bit 5 & high are invalid. */

            LM_INC64(&pdev->tx_info.stats.ipv6_lso_frames, 1);
        }
        else
        {
            flags |= TX_BD_FLAGS_SW_LSO;
            if(packet->u1.tx.flags & LM_TX_FLAG_TCP_LSO_SNAP_FRAME)
            {
                flags |= TX_BD_FLAGS_SW_SNAP;
            }

            DbgBreakIf(packet->u1.tx.lso_ip_hdr_len +
                packet->u1.tx.lso_tcp_hdr_len > 120);

            /* The size of IP and TCP options in term of 32-bit words. */
            flags |= (packet->u1.tx.lso_ip_hdr_len +
                packet->u1.tx.lso_tcp_hdr_len - 40) << 6;

            lso_bd_reserved = packet->u1.tx.lso_mss;

            LM_INC64(&pdev->tx_info.stats.ipv4_lso_frames, 1);
        }
    }
    else
    {
        lso_bd_reserved = 0;
    }

    start_bd = txq->prod_bd;
    frag = frags->frag_arr;

    /* Get the pointer to the current BD and its index. */
    prod_idx = txq->prod_idx;
    prod_bd = txq->prod_bd;

    /* This is the number of times we cross a BD page boundary for this
     * packet.  This and the bd_used value will give us the total number
     * of BD slots needed to send this packet which is used to determine
     * if a packet has been sent.  We only need this because unlike L2
     * completion, LSO completion does not end at a request boundary.
     * For example, if we had an LSO request that spans BD#100-120.  We
     * could get a transmit consumer index of 115. */
    packet->u1.tx.span_pages = 0;

    /* Initialize the bd's of this packet. */
    for(cnt = 0; cnt < frags->cnt; cnt++)
    {
        DbgBreakIf(frag->size >= 0x10000 || frag->size == 0);

        prod_bd->tx_bd_haddr_lo = frag->addr.as_u32.low;
        prod_bd->tx_bd_haddr_hi = frag->addr.as_u32.high;
        prod_bd->tx_bd_nbytes = (u16_t) frag->size;
        prod_bd->tx_bd_vlan_tag = packet->u1.tx.vlan_tag;
        prod_bd->tx_bd_flags = (u16_t) flags;

        if(packet->u1.tx.flags & LM_TX_FLAG_TCP_LSO_FRAME)
        {
            prod_bd->tx_bd_reserved = lso_bd_reserved;
        }
        else if(pdev->params.test_mode & TEST_MODE_TX_BD_TAGGING)
        {
            prod_bd->tx_bd_reserved = prod_idx & 0x0fff;
            prod_bd->tx_bd_reserved |= (u16_t) (GET_CID(txq->cid_addr) << 12);
        }

        packet->size += frag->size;

        last_bd = prod_bd;
        frag++;

        /* Advance to the next BD. */
        prod_bd++;
        prod_idx++;
        if((prod_idx & MAX_BD_PER_PAGE) == MAX_BD_PER_PAGE)
        {
            /* Only increment span_pages when this BDs for this request
             * cross a page boundary. */
            if(cnt+1 < frags->cnt)
            {
                packet->u1.tx.span_pages++;
            }

            prod_idx++;
            prod_bd = *((tx_bd_t **) ((tx_bd_next_t *)
                prod_bd)->tx_bd_next_reserved);
        }
    }

    /* Set the bd flags of the first and last BDs. */
    flags |= TX_BD_FLAGS_END;
    if(packet->u1.tx.flags & LM_TX_FLAG_COAL_NOW)
    {
        flags |= TX_BD_FLAGS_COAL_NOW;
    }

    last_bd->tx_bd_flags |= (u16_t) flags;
    start_bd->tx_bd_flags |= TX_BD_FLAGS_START;

    #if INCLUDE_OFLD_SUPPORT
    /* We need to do the padding for the catchup path. */
    if(chain_idx == pdev->tx_info.cu_idx &&
        packet->size < MIN_ETHERNET_PACKET_SIZE)
    {
        last_bd->tx_bd_nbytes +=
            (u16_t) (MIN_ETHERNET_PACKET_SIZE - packet->size);
        packet->size = MIN_ETHERNET_PACKET_SIZE;
    }
    #endif

    /* Save the number of BDs used.  Later we need to add this value back
     * to txq->bd_left when the packet is sent. */
    packet->u1.tx.bd_used = (u16_t) frags->cnt;

    packet->u1.tx.next_bd_idx = prod_idx;

    txq->prod_bd = prod_bd;
    txq->prod_idx = prod_idx;
    txq->prod_bseq += packet->size;
#if (DBG)
    if (chain_idx == pdev->tx_info.cu_idx)
    {
        DbgBreakIf(packet->size > pdev->params.mtu + 4);
    }
    else
    {
        DbgBreakIf(packet->size > pdev->params.mtu &&
            (flags & (TX_BD_FLAGS_SW_LSO | TX_BD_FLAGS_SW_FLAGS)) == 0);
    }
#endif
    s_list_push_tail(&txq->active_descq, &packet->link);

    if(!(packet->u1.tx.flags & LM_TX_FLAG_SKIP_MBQ_WRITE))
    {
        // hardcode offset in case of L2_ONLY (e.g Solaris)
        u32_t cmd_offset = 34*sizeof(u32_t); //  == OFFSETOF(l4_context_t, l4ctx_cmd)
        MBQ_WR16(
            pdev,
            GET_CID(txq->cid_addr),
            cmd_offset +
                OFFSETOF(tcp_context_cmd_cell_te_t, ccell_tx_host_bidx),
            txq->prod_idx);
        MBQ_WR32(
            pdev,
            GET_CID(txq->cid_addr),
            cmd_offset +
                OFFSETOF(tcp_context_cmd_cell_te_t, ccell_tx_host_bseq),
            txq->prod_bseq);
    }

    return LM_STATUS_SUCCESS;
} /* lm_send_packet */
#else
/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
lm_status_t
lm_send_packet(
    lm_device_t *pdev,
    u32_t chain_idx,
    lm_packet_t *packet,
    lm_frag_list_t *frags)
{
    u16_t lso_bd_reserved;
    u16_t ipv6_ext_len;
    lm_tx_chain_t *txq;
    tx_bd_t *start_bd;
    tx_bd_t *last_bd;
    tx_bd_t *prod_bd;
    lm_frag_t *frag;
    u16_t prod_idx;
    u32_t flags;
    u32_t cnt;
    lm_pkt_tx_info_t *pkt_info;

    txq = &pdev->tx_info.chain[chain_idx];

    if(packet == NULL)
    {
        // hardcode offset in case of L2_ONLY (e.g Solaris)
        u32_t cmd_offset = 34*sizeof(u32_t); //  == OFFSETOF(l4_context_t, l4ctx_cmd)
        MBQ_WR16(
            pdev,
            GET_CID(txq->cid_addr),
            cmd_offset +
                OFFSETOF(tcp_context_cmd_cell_te_t, ccell_tx_host_bidx),
            txq->prod_idx);
        if(pdev->vars.enable_cu_rate_limiter &&
           txq->idx == TX_CHAIN_IDX1)
        {
            REG_WR_IND(
                pdev,
                OFFSETOF(reg_space_t, com.com_scratch[0])+COM_HSI_OFFSETOFF(com_cu_host_bseq),
                txq->prod_bseq);
        }
        else
        {
                MBQ_WR32(
                pdev,
                GET_CID(txq->cid_addr),
                cmd_offset +
                OFFSETOF(tcp_context_cmd_cell_te_t, ccell_tx_host_bseq),
                txq->prod_bseq);
        }

        return LM_STATUS_SUCCESS;
    }

    #if DBG
    if(frags->cnt == 0)
    {
        DbgBreakMsg("zero frag_cnt\n");

        return LM_STATUS_INVALID_PARAMETER;
    }

    packet->u1.tx.dbg_start_bd = txq->prod_bd;
    packet->u1.tx.dbg_start_bd_idx = txq->prod_idx;
    packet->u1.tx.dbg_frag_cnt = (u16_t) frags->cnt;
    #endif

    last_bd = NULL;

    if(frags->cnt > txq->bd_left)
    {
        /* The caller should have done this check before calling this
         * routine. */
        DbgBreakMsg("No tx bd left.\n");

        return LM_STATUS_RESOURCE;
    }

    txq->bd_left -= (u16_t) frags->cnt;

    pkt_info = packet->u1.tx.tx_pkt_info;
    packet->u1.tx.size = 0;
    flags = 0;

    if(pkt_info->flags & LM_TX_FLAG_INSERT_VLAN_TAG)
    {
        flags |= TX_BD_FLAGS_VLAN_TAG;
    }

    if((pkt_info->flags & LM_TX_FLAG_TCP_LSO_FRAME) == 0)
    {
        if(pkt_info->flags & LM_TX_FLAG_COMPUTE_IP_CKSUM)
        {
            flags |= TX_BD_FLAGS_IP_CKSUM;
            LM_INC64(&pdev->tx_info.stats.ip_cso_frames, 1);
        }

        if(pkt_info->flags & LM_TX_FLAG_COMPUTE_TCP_UDP_CKSUM)
        {
            flags |= TX_BD_FLAGS_TCP_UDP_CKSUM;
            if(pkt_info->flags & LM_TX_FLAG_IPV6_PACKET)
            {
                LM_INC64(&pdev->tx_info.stats.ipv6_tcp_udp_cso_frames, 1);
            }
            else
            {
                LM_INC64(&pdev->tx_info.stats.ipv4_tcp_udp_cso_frames, 1);
            }
        }
    }

    if(pkt_info->flags & LM_TX_FLAG_DONT_COMPUTE_CRC)
    {
        flags |= TX_BD_FLAGS_DONT_GEN_CRC;
    }

    if(pkt_info->flags & LM_TX_FLAG_TCP_LSO_FRAME)
    {
        if(pkt_info->flags & LM_TX_FLAG_IPV6_PACKET)
        {
            /* TCP option length - bottom 4 bits of TX_BD_FLAGS_SW_OPTION_WORD
             *    in term of the number of 4-byte words.
             * IP header length - bits 1-2 of bd flag, the upper 2 bits of
             *    tx_bd_reserved, and the upper 1 bit of
             *    TX_BD_FLAGS_SW_OPTION_WORD will be used for IPV6 extension
             *    header length in term of 8-btye words.
             * TX_BD_FLAGS_SW_FLAGS bit will be used to indicate IPV6 LSO. */
            flags |= TX_BD_FLAGS_SW_FLAGS;

            if(pkt_info->flags & LM_TX_FLAG_TCP_LSO_SNAP_FRAME)
            {
                flags |= TX_BD_FLAGS_SW_SNAP;
            }

            DbgBreakIf(pkt_info->lso_tcp_hdr_len < 20 ||
                       pkt_info->lso_tcp_hdr_len > 84 ||
                       pkt_info->lso_tcp_hdr_len % 4);

            /* tcp option length in term of number of 32-bit word.  4 bits
             * are used for the number of words. */
            flags |= (pkt_info->lso_tcp_hdr_len - 20) << 6;

            DbgBreakIf(pkt_info->lso_ip_hdr_len < 20 ||
                       pkt_info->lso_ip_hdr_len > 296 ||
                      (pkt_info->lso_ip_hdr_len - 40) % 8);

            /* ipv6 extension header length.  6 bits are used for the number
             * of 64-bit words. */
            ipv6_ext_len = pkt_info->lso_ip_hdr_len - 40;

            DbgBreakIf(ipv6_ext_len & 0x7);

            /* ext_len in number of 8-byte words. */
            ipv6_ext_len >>= 3;

            flags |= (ipv6_ext_len & 0x3) << 1;             /* bit 1-0 */

            lso_bd_reserved = pkt_info->lso_mss;
            lso_bd_reserved |= (ipv6_ext_len & 0xc) << 12;  /* bit 3-2 */

            flags |= (ipv6_ext_len & 0x10) << 8;            /* bit 4  */

            DbgBreakIf(ipv6_ext_len >> 5);  /* bit 5 & high are invalid. */

            LM_INC64(&pdev->tx_info.stats.ipv6_lso_frames, 1);
        }
        else
        {
            flags |= TX_BD_FLAGS_SW_LSO;
            if(pkt_info->flags & LM_TX_FLAG_TCP_LSO_SNAP_FRAME)
            {
                flags |= TX_BD_FLAGS_SW_SNAP;
            }

            DbgBreakIf(pkt_info->lso_ip_hdr_len +
                pkt_info->lso_tcp_hdr_len > 120);

            /* The size of IP and TCP options in term of 32-bit words. */
            flags |= (pkt_info->lso_ip_hdr_len +
                pkt_info->lso_tcp_hdr_len - 40) << 6;

            lso_bd_reserved = pkt_info->lso_mss;

            LM_INC64(&pdev->tx_info.stats.ipv4_lso_frames, 1);
        }
    }
    else
    {
        lso_bd_reserved = 0;
    }

    start_bd = txq->prod_bd;
    frag = frags->frag_arr;

    /* Get the pointer to the current BD and its index. */
    prod_idx = txq->prod_idx;
    prod_bd = txq->prod_bd;

    /* This is the number of times we cross a BD page boundary for this
     * packet.  This and the bd_used value will give us the total number
     * of BD slots needed to send this packet which is used to determine
     * if a packet has been sent.  We only need this because unlike L2
     * completion, LSO completion does not end at a request boundary.
     * For example, if we had an LSO request that spans BD#100-120.  We
     * could get a transmit consumer index of 115. */
    packet->u1.tx.span_pages = 0;

    /* Initialize the bd's of this packet. */
    for(cnt = 0; cnt < frags->cnt; cnt++)
    {
        DbgBreakIf(frag->size >= 0x10000 || frag->size == 0);

        prod_bd->tx_bd_haddr_lo = frag->addr.as_u32.low;
        prod_bd->tx_bd_haddr_hi = frag->addr.as_u32.high;
        prod_bd->tx_bd_nbytes = (u16_t) frag->size;
        prod_bd->tx_bd_vlan_tag = pkt_info->vlan_tag;
        prod_bd->tx_bd_flags = (u16_t) flags;

        if(pkt_info->flags & LM_TX_FLAG_TCP_LSO_FRAME)
        {
            prod_bd->tx_bd_reserved = lso_bd_reserved;
        }
        else if(pdev->params.test_mode & TEST_MODE_TX_BD_TAGGING)
        {
            prod_bd->tx_bd_reserved = prod_idx & 0x0fff;
            prod_bd->tx_bd_reserved |= (u16_t) (GET_CID(txq->cid_addr) << 12);
        }

        packet->u1.tx.size += frag->size;

        last_bd = prod_bd;
        frag++;

        /* Advance to the next BD. */
        prod_bd++;
        prod_idx++;
        if((prod_idx & MAX_BD_PER_PAGE) == MAX_BD_PER_PAGE)
        {
            /* Only increment span_pages when this BDs for this request
             * cross a page boundary. */
            if(cnt+1 < frags->cnt)
            {
                packet->u1.tx.span_pages++;
            }

            prod_idx++;
            prod_bd = *((tx_bd_t **) ((tx_bd_next_t *)
                prod_bd)->tx_bd_next_reserved);
        }
    }

    /* Set the bd flags of the first and last BDs. */
    flags |= TX_BD_FLAGS_END;
    if(pkt_info->flags & LM_TX_FLAG_COAL_NOW)
    {
        flags |= TX_BD_FLAGS_COAL_NOW;
    }

    last_bd->tx_bd_flags |= (u16_t) flags;
    start_bd->tx_bd_flags |= TX_BD_FLAGS_START;

    #if INCLUDE_OFLD_SUPPORT
    /* We need to do the padding for the catchup path. */
    if(chain_idx == pdev->tx_info.cu_idx &&
        packet->u1.tx.size < MIN_ETHERNET_PACKET_SIZE)
    {
        last_bd->tx_bd_nbytes +=
            (u16_t) (MIN_ETHERNET_PACKET_SIZE - packet->u1.tx.size);
        packet->u1.tx.size = MIN_ETHERNET_PACKET_SIZE;
    }
    #endif

    /* Save the number of BDs used.  Later we need to add this value back
     * to txq->bd_left when the packet is sent. */
    packet->u1.tx.bd_used = (u16_t) frags->cnt;

    packet->u1.tx.next_bd_idx = prod_idx;

    txq->prod_bd = prod_bd;
    txq->prod_idx = prod_idx;
    txq->prod_bseq += packet->u1.tx.size;
#if (DBG)
    if (chain_idx == pdev->tx_info.cu_idx)
    {
        DbgBreakIf(packet->u1.tx.size > pdev->params.mtu + 4);
    }
    else
    {
        DbgBreakIf(packet->u1.tx.size > pdev->params.mtu &&
            (flags & (TX_BD_FLAGS_SW_LSO | TX_BD_FLAGS_SW_FLAGS)) == 0);
    }
#endif
    s_list_push_tail(&txq->active_descq, &packet->link);

    if(!(pkt_info->flags & LM_TX_FLAG_SKIP_MBQ_WRITE))
    {
        // hardcode offset in case of L2_ONLY (e.g Solaris)
        u32_t cmd_offset = 34*sizeof(u32_t); //  == OFFSETOF(l4_context_t, l4ctx_cmd)
        MBQ_WR16(
            pdev,
            GET_CID(txq->cid_addr),
            cmd_offset +
                OFFSETOF(tcp_context_cmd_cell_te_t, ccell_tx_host_bidx),
            txq->prod_idx);
        if(pdev->vars.enable_cu_rate_limiter &&
           txq->idx == TX_CHAIN_IDX1)
        {
            REG_WR_IND(
                pdev,
                OFFSETOF(reg_space_t, com.com_scratch[0])+COM_HSI_OFFSETOFF(com_cu_host_bseq),
                txq->prod_bseq);
        }
        else
        {
                MBQ_WR32(
                pdev,
                GET_CID(txq->cid_addr),
                cmd_offset +
                OFFSETOF(tcp_context_cmd_cell_te_t, ccell_tx_host_bseq),
                txq->prod_bseq);
        }
    }

    return LM_STATUS_SUCCESS;
} /* lm_send_packet */
#endif /* LM_NON_LEGACY_MODE_SUPPORT */


/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
STATIC u32_t
get_packets_sent(
    struct _lm_device_t *pdev,
    lm_tx_chain_t *txq,
    u16_t hw_con_idx,
    s_list_t *sent_list)
{
    lm_packet_t *pkt;
    u32_t pkt_cnt;

    /* The consumer index may stop at the end of a page boundary.
     * In this case, we need to advance the next to the next one. */
    if((hw_con_idx & MAX_BD_PER_PAGE) == MAX_BD_PER_PAGE)
    {
        hw_con_idx++;
    }

    pkt_cnt = 0;

    while(txq->con_idx != hw_con_idx)
    {
        DbgBreakIf(S16_SUB(hw_con_idx, txq->con_idx) <= 0);

        pkt = (lm_packet_t *) s_list_peek_head(&txq->active_descq);

        DbgBreakIf(pkt == NULL);

        if(!pkt)
                {
                        DbgBreakIf(!s_list_is_empty(&txq->active_descq));
                        break;
                }
        /* LSO requests may not complete at the request boundary.
         *
         * if(pkt->u1.tx.flags & LM_TX_FLAG_TCP_LSO_FRAME) */
        {
            if((u16_t) S16_SUB(hw_con_idx, txq->con_idx) <
                pkt->u1.tx.bd_used + pkt->u1.tx.span_pages)
            {
                break;
            }
        }

        #if DBG
        DbgBreakIf(pkt->u1.tx.dbg_start_bd_idx != txq->con_idx);

        /* Make sure hw_con_idx ends at an l2 packet boundary.  For LSO,
         * request, hw_con_idx may not end at the request boundary. */
        while(pkt)
        {
            if(S16_SUB(hw_con_idx, pkt->u1.tx.next_bd_idx) <= 0)
            {
                break;
            }

            pkt = (lm_packet_t *) s_list_next_entry(&pkt->link);
        }

        DbgBreakIf(pkt == NULL);

        /* catchup workaround.
         * DbgBreakIf(
         *    !(pkt->u1.tx.flags & LM_TX_FLAG_TCP_LSO_FRAME) &&
         *    (hw_con_idx != pkt->u1.tx.next_bd_idx)); */
        #endif

        pkt = (lm_packet_t *) s_list_pop_head(&txq->active_descq);

        /* Advance the txq->con_idx to the start bd_idx of the next packet. */
        txq->con_idx = pkt->u1.tx.next_bd_idx;

        pkt->status = LM_STATUS_SUCCESS;

        txq->bd_left += pkt->u1.tx.bd_used;

        s_list_push_tail(sent_list, &pkt->link);

        pkt_cnt++;
    }

    return pkt_cnt;
} /* get_packets_sent */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
u32_t
lm_get_packets_sent(
    struct _lm_device_t *pdev,
    u32_t qidx,
    u32_t con_idx,
    s_list_t *sent_list)
{
    lm_tx_chain_t *txq;
    u16_t hw_con_idx;
    u32_t pkts_added;
    u32_t pkt_cnt;

    txq = &pdev->tx_info.chain[qidx];

    if(con_idx)
    {
        hw_con_idx = con_idx & 0xffff;

        pkt_cnt = get_packets_sent(pdev, txq, hw_con_idx, sent_list);
    }
    else
    {
        pkt_cnt = 0;

        for(; ;)
        {
            hw_con_idx = *txq->hw_con_idx_ptr;

            pkts_added = get_packets_sent(pdev, txq, hw_con_idx, sent_list);
            if(pkts_added == 0)
            {
                break;
            }

            pkt_cnt += pkts_added;
        }
    }

    return pkt_cnt;
} /* lm_get_packets_sent */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
void
lm_service_tx_int(
    lm_device_t *pdev,
    u32_t chain_idx)
{
    lm_packet_t *pkt_arr[MAX_PACKETS_PER_INDICATION];
    lm_packet_t **pkt_arr_ptr;
    s_list_t sent_list;
    lm_packet_t *pkt;
    u32_t pkt_cnt;

    s_list_init(&sent_list, NULL, NULL, 0);

    (void) lm_get_packets_sent(pdev, chain_idx, 0, &sent_list);

    while(!s_list_is_empty(&sent_list))
    {
        pkt_arr_ptr = pkt_arr;

        for(pkt_cnt = 0; pkt_cnt < MAX_PACKETS_PER_INDICATION; pkt_cnt++)
        {
            pkt = (lm_packet_t *) s_list_pop_head(&sent_list);
            if(pkt == NULL)
            {
                break;
            }

            *pkt_arr_ptr = pkt;
            pkt_arr_ptr++;
        }

        mm_indicate_tx(pdev, chain_idx, pkt_arr, pkt_cnt);
    }
} /* lm_service_tx_int */



/*******************************************************************************
 * Description:
 *
 * Return:
 ******************************************************************************/
void
lm_send_abort(
    struct _lm_device_t *pdev,
    u32_t idx)
{
    lm_tx_chain_t *txq;
    lm_packet_t *pkt;

    DbgBreakIf(idx >= pdev->tx_info.num_txq);

    txq = &pdev->tx_info.chain[idx];

    for(; ;)
    {
        pkt = (lm_packet_t *) s_list_pop_head(&txq->active_descq);
        if(pkt == NULL)
        {
            break;
        }

        pkt->status = LM_STATUS_ABORTED;
        pdev->tx_info.stats.aborted++;
        txq->bd_left += pkt->u1.tx.bd_used;

        mm_indicate_tx(pdev, idx, &pkt, 1);
    }

    DbgBreakIf(txq->bd_left !=
        pdev->params.l2_tx_bd_page_cnt[txq->idx] * MAX_BD_PER_PAGE - 1);
} /* lm_send_abort */