root/usr/src/uts/common/io/nge/nge_rx.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 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "nge.h"

#undef  NGE_DBG
#define NGE_DBG         NGE_DBG_RECV

#define RXD_END         0x20000000
#define RXD_ERR         0x40000000
#define RXD_OWN         0x80000000
#define RXD_CSUM_MSK    0x1C000000
#define RXD_BCNT_MSK    0x00003FFF

#define RXD_CK8G_NO_HSUM        0x0
#define RXD_CK8G_TCP_SUM_ERR    0x04000000
#define RXD_CK8G_UDP_SUM_ERR    0x08000000
#define RXD_CK8G_IP_HSUM_ERR    0x0C000000
#define RXD_CK8G_IP_HSUM        0x10000000
#define RXD_CK8G_TCP_SUM        0x14000000
#define RXD_CK8G_UDP_SUM        0x18000000
#define RXD_CK8G_RESV           0x1C000000

extern ddi_device_acc_attr_t nge_data_accattr;

/*
 * Callback code invoked from STREAMs when the recv data buffer is free for
 * recycling.
 *
 * The following table describes function behaviour:
 *
 *                      | mac stopped | mac running
 * ---------------------------------------------------
 * buffer delivered     | free buffer | recycle buffer
 * buffer not delivered | do nothing  | recycle buffer (*)
 *
 * Note (*):
 *   Recycle buffer only if mac state did not change during execution of
 *   function. Otherwise if mac state changed, set buffer delivered & re-enter
 *   function by calling freemsg().
 */

void
nge_recv_recycle(caddr_t arg)
{
        boolean_t val;
        boolean_t valid;
        nge_t *ngep;
        dma_area_t *bufp;
        buff_ring_t *brp;
        nge_sw_statistics_t *sw_stp;

        bufp = (dma_area_t *)arg;
        ngep = (nge_t *)bufp->private;
        brp = ngep->buff;
        sw_stp = &ngep->statistics.sw_statistics;

        /*
         * Free the buffer directly if the buffer was allocated
         * previously or mac was stopped.
         */
        if (bufp->signature != brp->buf_sign) {
                if (bufp->rx_delivered == B_TRUE) {
                        nge_free_dma_mem(bufp);
                        kmem_free(bufp, sizeof (dma_area_t));
                        val = nge_atomic_decrease(&brp->rx_hold, 1);
                        ASSERT(val == B_TRUE);
                }
                return;
        }

        /*
         * recycle the data buffer again and fill them in free ring
         */
        bufp->rx_recycle.free_func = nge_recv_recycle;
        bufp->rx_recycle.free_arg = (caddr_t)bufp;

        bufp->mp = desballoc(DMA_VPTR(*bufp),
            ngep->buf_size + NGE_HEADROOM, 0, &bufp->rx_recycle);

        if (bufp->mp == NULL) {
                sw_stp->mp_alloc_err++;
                sw_stp->recy_free++;
                nge_free_dma_mem(bufp);
                kmem_free(bufp, sizeof (dma_area_t));
                val = nge_atomic_decrease(&brp->rx_hold, 1);
                ASSERT(val == B_TRUE);
        } else {

                mutex_enter(brp->recycle_lock);
                if (bufp->signature != brp->buf_sign)
                        valid = B_TRUE;
                else
                        valid = B_FALSE;
                bufp->rx_delivered = valid;
                if (bufp->rx_delivered == B_FALSE)  {
                        bufp->next = brp->recycle_list;
                        brp->recycle_list = bufp;
                }
                mutex_exit(brp->recycle_lock);
                if (valid == B_TRUE)
                        /* call nge_rx_recycle again to free it */
                        freemsg(bufp->mp);
                else {
                        val = nge_atomic_decrease(&brp->rx_hold, 1);
                        ASSERT(val == B_TRUE);
                }
        }
}

/*
 * Checking the rx's BDs (one or more) to receive
 * one complete packet.
 * start_index: the start indexer of BDs for one packet.
 * end_index: the end indexer of BDs for one packet.
 */
static mblk_t *nge_recv_packet(nge_t *ngep, uint32_t start_index, size_t len);

static mblk_t *
nge_recv_packet(nge_t *ngep, uint32_t start_index, size_t len)
{
        uint8_t *rptr;
        uint32_t minsize;
        uint32_t maxsize;
        mblk_t *mp;
        buff_ring_t *brp;
        sw_rx_sbd_t *srbdp;
        dma_area_t *bufp;
        nge_sw_statistics_t *sw_stp;
        void *hw_bd_p;

        brp = ngep->buff;
        minsize = ETHERMIN;
        maxsize = ngep->max_sdu;
        sw_stp = &ngep->statistics.sw_statistics;
        mp = NULL;

        srbdp = &brp->sw_rbds[start_index];
        DMA_SYNC(*srbdp->bufp, DDI_DMA_SYNC_FORKERNEL);
        hw_bd_p = DMA_VPTR(srbdp->desc);

        /*
         * First check the free_list, if it is NULL,
         * make the recycle_list be free_list.
         */
        if (brp->free_list == NULL) {
                mutex_enter(brp->recycle_lock);
                brp->free_list = brp->recycle_list;
                brp->recycle_list = NULL;
                mutex_exit(brp->recycle_lock);
        }
        bufp = brp->free_list;
        /* If it's not a qualified packet, delete it */
        if (len > maxsize || len < minsize) {
                ngep->desc_attr.rxd_fill(hw_bd_p, &srbdp->bufp->cookie,
                    srbdp->bufp->alength);
                srbdp->flags = CONTROLER_OWN;
                return (NULL);
        }

        /*
         * If receive packet size is smaller than RX bcopy threshold,
         * or there is no available buffer in free_list or recycle list,
         * we use bcopy directly.
         */
        if (len <= ngep->param_rxbcopy_threshold || bufp == NULL)
                brp->rx_bcopy = B_TRUE;
        else
                brp->rx_bcopy = B_FALSE;

        if (brp->rx_bcopy) {
                mp = allocb(len + NGE_HEADROOM, 0);
                if (mp == NULL) {
                        sw_stp->mp_alloc_err++;
                        ngep->desc_attr.rxd_fill(hw_bd_p, &srbdp->bufp->cookie,
                            srbdp->bufp->alength);
                        srbdp->flags = CONTROLER_OWN;
                        return (NULL);
                }
                rptr = DMA_VPTR(*srbdp->bufp);
                mp->b_rptr = mp->b_rptr + NGE_HEADROOM;
                bcopy(rptr + NGE_HEADROOM, mp->b_rptr, len);
                mp->b_wptr = mp->b_rptr + len;
        } else {
                mp = srbdp->bufp->mp;
                /*
                 * Make sure the packet *contents* 4-byte aligned
                 */
                mp->b_rptr += NGE_HEADROOM;
                mp->b_wptr = mp->b_rptr + len;
                mp->b_next = mp->b_cont = NULL;
                srbdp->bufp->rx_delivered = B_TRUE;
                srbdp->bufp = NULL;
                nge_atomic_increase(&brp->rx_hold, 1);

                /* Fill the buffer from free_list */
                srbdp->bufp = bufp;
                brp->free_list = bufp->next;
                bufp->next = NULL;
        }

        /* replenish the buffer for hardware descriptor */
        ngep->desc_attr.rxd_fill(hw_bd_p, &srbdp->bufp->cookie,
            srbdp->bufp->alength);
        srbdp->flags = CONTROLER_OWN;
        sw_stp->rbytes += len;
        sw_stp->recv_count++;

        return (mp);
}


#define RX_HW_ERR       0x01
#define RX_SUM_NO       0x02
#define RX_SUM_ERR      0x04

/*
 * Statistic the rx's error
 * and generate a log msg for these.
 * Note:
 * RXE, Parity Error, Symbo error, CRC error
 * have been recored by nvidia's  hardware
 * statistics part (nge_statistics). So it is uncessary to record them by
 * driver in this place.
 */
static uint32_t
nge_rxsta_handle(nge_t *ngep, uint32_t stflag, uint32_t *pflags);

static uint32_t
nge_rxsta_handle(nge_t *ngep,  uint32_t stflag, uint32_t *pflags)
{
        uint32_t errors;
        uint32_t err_flag;
        nge_sw_statistics_t *sw_stp;

        err_flag = 0;
        sw_stp = &ngep->statistics.sw_statistics;

        if ((RXD_END & stflag) == 0)
                return (RX_HW_ERR);

        errors = stflag & RXD_CSUM_MSK;
        switch (errors) {
        default:
        break;

        case RXD_CK8G_TCP_SUM:
        case RXD_CK8G_UDP_SUM:
                *pflags |= HCK_IPV4_HDRCKSUM_OK;
                *pflags |= HCK_FULLCKSUM_OK;
                break;

        case RXD_CK8G_TCP_SUM_ERR:
        case RXD_CK8G_UDP_SUM_ERR:
                sw_stp->tcp_hwsum_err++;
                *pflags |= HCK_IPV4_HDRCKSUM_OK;
                break;

        case RXD_CK8G_IP_HSUM:
                *pflags |= HCK_IPV4_HDRCKSUM_OK;
                break;

        case RXD_CK8G_NO_HSUM:
                err_flag |= RX_SUM_NO;
                break;

        case RXD_CK8G_IP_HSUM_ERR:
                sw_stp->ip_hwsum_err++;
                err_flag |=  RX_SUM_ERR;
                break;
        }

        if ((stflag & RXD_ERR) != 0)    {

                err_flag |= RX_HW_ERR;
                NGE_DEBUG(("Receive desc error, status: 0x%x", stflag));
        }

        return (err_flag);
}

static mblk_t *
nge_recv_ring(nge_t *ngep)
{
        uint32_t stflag;
        uint32_t flag_err;
        uint32_t sum_flags;
        size_t len;
        uint64_t end_index;
        uint64_t sync_start;
        mblk_t *mp;
        mblk_t **tail;
        mblk_t *head;
        recv_ring_t *rrp;
        buff_ring_t *brp;
        sw_rx_sbd_t *srbdp;
        void * hw_bd_p;
        nge_mode_cntl mode_cntl;

        mp = NULL;
        head = NULL;
        tail = &head;
        rrp = ngep->recv;
        brp = ngep->buff;

        end_index = sync_start = rrp->prod_index;
        /* Sync the descriptor for kernel */
        if (sync_start + ngep->param_recv_max_packet <= ngep->rx_desc) {
                (void) ddi_dma_sync(rrp->desc.dma_hdl,
                    sync_start * ngep->desc_attr.rxd_size,
                    ngep->param_recv_max_packet * ngep->desc_attr.rxd_size,
                    DDI_DMA_SYNC_FORKERNEL);
        } else {
                (void) ddi_dma_sync(rrp->desc.dma_hdl,
                    sync_start * ngep->desc_attr.rxd_size,
                    0,
                    DDI_DMA_SYNC_FORKERNEL);
                (void) ddi_dma_sync(rrp->desc.dma_hdl,
                    0,
                    (ngep->param_recv_max_packet + sync_start - ngep->rx_desc) *
                    ngep->desc_attr.rxd_size,
                    DDI_DMA_SYNC_FORKERNEL);
        }

        /*
         * Looking through the rx's ring to find the good packets
         * and try to receive more and more packets in rx's ring
         */
        for (;;) {
                sum_flags = 0;
                flag_err = 0;
                end_index = rrp->prod_index;
                srbdp = &brp->sw_rbds[end_index];
                hw_bd_p = DMA_VPTR(srbdp->desc);
                stflag = ngep->desc_attr.rxd_check(hw_bd_p, &len);
                /*
                 * If there is no packet in receving ring
                 * break the loop
                 */
                if ((stflag & RXD_OWN) != 0 || HOST_OWN == srbdp->flags)
                        break;

                ngep->recv_count++;
                flag_err = nge_rxsta_handle(ngep, stflag, &sum_flags);
                if ((flag_err & RX_HW_ERR) == 0) {
                        srbdp->flags = NGE_END_PACKET;
                        mp = nge_recv_packet(ngep, end_index, len);
                } else {
                        /* Hardware error, re-use the buffer */
                        ngep->desc_attr.rxd_fill(hw_bd_p, &srbdp->bufp->cookie,
                            srbdp->bufp->alength);
                        srbdp->flags = CONTROLER_OWN;
                }
                if (mp != NULL) {
                        if (!(flag_err & (RX_SUM_NO | RX_SUM_ERR))) {
                                mac_hcksum_set(mp, 0, 0, 0, 0, sum_flags);
                        }
                        *tail = mp;
                        tail = &mp->b_next;
                        mp = NULL;
                }
                rrp->prod_index = NEXT(end_index, rrp->desc.nslots);
                if (ngep->recv_count >= ngep->param_recv_max_packet)
                        break;
        }

        /* Sync the descriptors for device */
        if (sync_start + ngep->recv_count <= ngep->rx_desc) {
                (void) ddi_dma_sync(rrp->desc.dma_hdl,
                    sync_start * ngep->desc_attr.rxd_size,
                    ngep->recv_count * ngep->desc_attr.rxd_size,
                    DDI_DMA_SYNC_FORDEV);
        } else {
                (void) ddi_dma_sync(rrp->desc.dma_hdl,
                    sync_start * ngep->desc_attr.rxd_size,
                    0,
                    DDI_DMA_SYNC_FORDEV);
                (void) ddi_dma_sync(rrp->desc.dma_hdl,
                    0,
                    (ngep->recv_count + sync_start - ngep->rx_desc) *
                    ngep->desc_attr.rxd_size,
                    DDI_DMA_SYNC_FORDEV);
        }
        mode_cntl.mode_val = nge_reg_get32(ngep, NGE_MODE_CNTL);
        mode_cntl.mode_bits.rxdm = NGE_SET;
        mode_cntl.mode_bits.tx_rcom_en = NGE_SET;
        nge_reg_put32(ngep, NGE_MODE_CNTL, mode_cntl.mode_val);

        return (head);
}

void
nge_receive(nge_t *ngep)
{
        mblk_t *mp;
        recv_ring_t *rrp;
        rrp = ngep->recv;

        mp = nge_recv_ring(ngep);
        mutex_exit(ngep->genlock);
        if (mp != NULL)
                mac_rx(ngep->mh, rrp->handle, mp);
        mutex_enter(ngep->genlock);
}

void
nge_hot_rxd_fill(void *hwd, const ddi_dma_cookie_t *cookie, size_t len)
{
        uint64_t dmac_addr;
        hot_rx_bd * hw_bd_p;

        hw_bd_p = (hot_rx_bd *)hwd;
        dmac_addr = cookie->dmac_laddress + NGE_HEADROOM;

        hw_bd_p->cntl_status.cntl_val = 0;

        hw_bd_p->host_buf_addr_hi = dmac_addr >> 32;
        hw_bd_p->host_buf_addr_lo = (uint32_t)dmac_addr;
        hw_bd_p->cntl_status.control_bits.bcnt = len - 1;

        membar_producer();
        hw_bd_p->cntl_status.control_bits.own = NGE_SET;
}

void
nge_sum_rxd_fill(void *hwd, const ddi_dma_cookie_t *cookie, size_t len)
{
        sum_rx_bd * hw_bd_p;

        hw_bd_p = hwd;

        hw_bd_p->cntl_status.cntl_val = 0;

        hw_bd_p->host_buf_addr =
            (uint32_t)(cookie->dmac_address + NGE_HEADROOM);
        hw_bd_p->cntl_status.control_bits.bcnt = len - 1;

        membar_producer();
        hw_bd_p->cntl_status.control_bits.own = NGE_SET;
}

uint32_t
nge_hot_rxd_check(const void *hwd, size_t *len)
{
        uint32_t err_flag;
        const hot_rx_bd * hrbdp;

        hrbdp = hwd;
        err_flag = hrbdp->cntl_status.cntl_val;
        *len = err_flag & RXD_BCNT_MSK;
        return (err_flag);
}

uint32_t
nge_sum_rxd_check(const void *hwd, size_t *len)
{
        uint32_t err_flag;
        const sum_rx_bd * hrbdp;

        hrbdp = hwd;

        err_flag = hrbdp->cntl_status.cntl_val;
        *len = err_flag & RXD_BCNT_MSK;
        return (err_flag);
}