root/usr/src/uts/common/io/bnx/bnxhwi.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.
 */

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2019, Joyent, Inc.
 */

#include "bnx.h"
#include "bnx_mm.h"
#include "bnxsnd.h"
#include "bnxrcv.h"
#include "bnxint.h"
#include "bnxtmr.h"
#include "bnxcfg.h"

void
bnx_update_phy(um_device_t * const umdevice)
{
        lm_status_t lmstatus;
        lm_device_t *lmdevice;

        lmdevice = &(umdevice->lm_dev);

        /* Map 'ndd' parameters to LM struct. */
        bnx_cfg_map_phy(umdevice);

        mutex_enter(&umdevice->os_param.phy_mutex);

        /* Reset, re-program and bring-up phy. */
        lmstatus = lm_init_phy(lmdevice, lmdevice->params.req_medium,
            lmdevice->params.flow_ctrl_cap, lmdevice->params.selective_autoneg,
            lmdevice->params.wire_speed, 0);
        if (lmstatus != LM_STATUS_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to configure the PHY.",
                    umdevice->dev_name);
        }

        lm_service_phy_int(lmdevice, TRUE);

        mutex_exit(&umdevice->os_param.phy_mutex);
}

ddi_dma_handle_t *
bnx_find_dma_hdl(um_device_t *const umdevice, const void *const virtaddr)
{
        int i;
        ddi_dma_handle_t *dmahdl;

        dmahdl = NULL;
        for (i = 0; i < umdevice->os_param.dma_handles_used; i++) {
                if (umdevice->os_param.dma_virt[i] == virtaddr) {
                        dmahdl = &(umdevice->os_param.dma_handle[i]);
                }
        }

        return (dmahdl);
}

static void
bnx_free_lmmem(um_device_t * const umdevice)
{
        int i;
        bnx_memreq_t *memreq;
        ddi_dma_handle_t *dma_handle;
        ddi_acc_handle_t *acc_handle;

        if (umdevice->os_param.dma_handles_used != 0) {
                i = umdevice->os_param.dma_handles_used - 1;

                dma_handle = &(umdevice->os_param.dma_handle[i]);
                acc_handle = &(umdevice->os_param.dma_acc_handle[i]);

                /* Free all shared memory. */
                for (; i >= 0; i--) {
                        (void) ddi_dma_unbind_handle(*dma_handle);

                        ddi_dma_mem_free(acc_handle);

                        ddi_dma_free_handle(dma_handle);

                        dma_handle--;
                        acc_handle--;
                }

                umdevice->os_param.dma_handles_used = 0;
        }

        if (umdevice->memcnt != 0) {
                /* Free all local memory. */
                for (i = umdevice->memcnt - 1; i >= 0; i--) {
                        memreq = &umdevice->memreq[i];

                        kmem_free(memreq->addr, memreq->size);

                        memreq->addr = NULL;
                        memreq->size = 0;
                }

                umdevice->memcnt = 0;
        }
}

int
bnx_hdwr_init(um_device_t *const umdevice)
{
        lm_status_t lmstatus;
        lm_device_t *lmdevice;

        lmdevice = &(umdevice->lm_dev);

        lmstatus = lm_get_dev_info(lmdevice);
        if (lmstatus != LM_STATUS_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to get device information.\n",
                    umdevice->dev_name);
                return (-1);
        }

        /*
         * Initialize the adapter resource.  Mainly allocating memory needed
         * by the driver, such as packet descriptors, shared memory, etc.
         */
        lmstatus = lm_init_resc(lmdevice);
        if (lmstatus != LM_STATUS_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to allocate device resources.\n",
                    umdevice->dev_name);
                goto error1;
        }

        if (bnx_txpkts_init(umdevice)) {
                goto error1;
        }

        if (bnx_rxpkts_init(umdevice)) {
                goto error2;
        }

        /* Find the DMA handle associated with the status block memory. */
        umdevice->os_param.status_block_dma_hdl = bnx_find_dma_hdl(umdevice,
            (void *)(umdevice->lm_dev.vars.status_virt));

        /* Reset the local interrupt event index. */
        umdevice->dev_var.processed_status_idx = 0;

        /* Initialize the receive mask to a sane default. */
        umdevice->dev_var.rx_filter_mask = LM_RX_MASK_ACCEPT_UNICAST |
            LM_RX_MASK_ACCEPT_BROADCAST;

        return (0);

error2:
        bnx_txpkts_fini(umdevice);

error1:
        bnx_free_lmmem(umdevice);

        return (-1);
}

int
bnx_hdwr_acquire(um_device_t *const umdevice)
{
        lm_status_t lmstatus;
        lm_device_t *lmdevice;

        lmdevice = &(umdevice->lm_dev);

        /* Reset the configuration to the hardware default. */
        bnx_cfg_reset(umdevice);

        /*
         * A call to lm_reset() implicitly means we are relieving the firmware
         * of it's responsibility to maintain the device.  The driver assumes
         * control.  The LM vars.medium field normally gets set with a call to
         * lm_init_phy(), but this function cannot be called before we assume
         * control of the device.  If we did, we run the risk of contending
         * with the firmware for PHY accesses.  Do the next best thing.
         */
        lmdevice->vars.medium = lm_get_medium(lmdevice);

        /* Map 'ndd' parameters to LM struct. */
        bnx_cfg_map_phy(umdevice);

        /* Bring the chip under driver control. */
        lmstatus = lm_reset(lmdevice, LM_REASON_DRIVER_RESET);
        if (lmstatus != LM_STATUS_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to reset chip.\n",
                    umdevice->dev_name);
                return (-1);
        }

        /* Configure the PHY to the requested settings. */
        lmstatus = lm_init_phy(lmdevice, lmdevice->params.req_medium,
            lmdevice->params.flow_ctrl_cap, lmdevice->params.selective_autoneg,
            lmdevice->params.wire_speed, 0);
        if (lmstatus != LM_STATUS_SUCCESS) {
                cmn_err(CE_WARN, "%s: Failed to initialize the PHY.",
                    umdevice->dev_name);
        }

        lm_service_phy_int(lmdevice, FALSE); /* force a phy status update */

        umdevice->dev_var.indLink = lmdevice->vars.link_status;
        umdevice->dev_var.indMedium = lmdevice->vars.medium;

        /*
         * Need to clear TX PATCH scratch register at offset 0x420
         * to instruct chip to do full TCP checksum calculations.
         */
        REG_WR_IND(lmdevice, (OFFSETOF(reg_space_t, tpat.tpat_scratch[0]) +
            0x420), 0);

        FLUSHPOSTEDWRITES(lmdevice);

        umdevice->recv_discards = 0;

        /* Make sure the rx statistics counters are reset. */
        bzero(&(lmdevice->rx_info.stats), sizeof (lm_rx_stats_t));

        /* Post rx buffers to the chip. */
        (void) lm_post_buffers(lmdevice, 0, NULL);

        /* Allow the hardware to accept rx traffic. */
        (void) lm_set_rx_mask(lmdevice, RX_FILTER_USER_IDX0,
            umdevice->dev_var.rx_filter_mask);

        FLUSHPOSTEDWRITES(lmdevice);

        /* Enable interrupts. */
        bnx_intr_enable(umdevice);

        /* Start the periodic timer. */
        bnx_timer_start(umdevice);

        return (0);
}

void
bnx_hdwr_release(um_device_t *const umdevice)
{
        int reason;
        lm_device_t *lmdevice;

        lmdevice = &(umdevice->lm_dev);

        /* Stop the periodic timer. */
        bnx_timer_stop(umdevice);

        /* Disable interrupts. */
        bnx_intr_disable(umdevice);

        /*
         * In Solaris when RX traffic is accepted, the system might generate
         * and attempt to send some TX packets (from within gld_recv() !).
         * Claiming any TX locks before this point would create a deadlock.
         * The ISR would be waiting for a lock acquired here that would never
         * be freed, since we in-turn would be waiting for the ISR to finish
         * here.  Consequently, we acquire the TX lock as soon as we know that
         * no TX traffic is a result of RX traffic.
         */
        rw_enter(&umdevice->os_param.gld_snd_mutex, RW_WRITER);

        /* Set RX mask to stop receiving any further packets */
        (void) lm_set_rx_mask(lmdevice, RX_FILTER_USER_IDX0,
            LM_RX_MASK_ACCEPT_NONE);

        FLUSHPOSTEDWRITES(lmdevice);

        if (umdevice->dev_var.fw_ver < FW_VER_WITH_UNLOAD_POWER_DOWN) {
                reason = LM_REASON_DRIVER_SHUTDOWN;
        } else {
                reason = LM_REASON_DRIVER_UNLOAD_POWER_DOWN;
        }

        lm_chip_reset(lmdevice, reason);

        FLUSHPOSTEDWRITES(lmdevice);

        /* Reclaim all tx buffers submitted to the hardware. */
        bnx_txpkts_flush(umdevice);

        /* Reclaim all rx buffers submitted to the hardware. */
        bnx_rxpkts_recycle(umdevice);

        rw_exit(&umdevice->os_param.gld_snd_mutex);
}

void
bnx_hdwr_fini(um_device_t *const umdevice)
{
        bnx_rxpkts_fini(umdevice);

        bnx_txpkts_fini(umdevice);

        bnx_free_lmmem(umdevice);
}

static u32_t
compute_crc32(const u8_t *const buf, u32_t buf_size)
{
        u32_t reg;
        u32_t tmp;
        u32_t j;
        u32_t k;

        reg = 0xffffffff;

        for (j = 0; j < buf_size; j++) {
                reg ^= buf[j];

                for (k = 0; k < 8; k++) {
                        tmp = reg & 0x01;

                        reg >>= 1;

                        if (tmp) {
                                reg ^= 0xedb88320;
                        }
                }
        }

        return (~reg);
}

int
bnx_find_mchash_collision(lm_mc_table_t *mc_table, const uint8_t *const mc_addr)
{
        u32_t cur_bit_pos;
        u32_t tgt_bit_pos;
        u32_t idx;
        u32_t crc32;

        crc32 = compute_crc32(mc_addr, ETHERNET_ADDRESS_SIZE);

        tgt_bit_pos = ~crc32 & 0xff;

        for (idx = 0; idx < mc_table->entry_cnt; idx++) {
                crc32 = compute_crc32(mc_table->addr_arr[idx].mc_addr,
                    ETHERNET_ADDRESS_SIZE);

                /*
                 * The most significant 7 bits of the CRC32 (no inversion),
                 * are used to index into one of the possible 128 bit positions.
                 */
                cur_bit_pos = ~crc32 & 0xff;

                if (tgt_bit_pos == cur_bit_pos) {
                        return (idx);
                }
        }

        return (-1);
}



/*
 * Name:        um_send_driver_pulse
 *
 * Input:       ptr to driver structure
 *
 * Return:      none
 *
 * Description: um_send_driver_pulse routine sends heartbeat pulse to firmware.
 */
void
um_send_driver_pulse(um_device_t *const umdevice)
{
        u32_t msg_code;
        u32_t offset;
        lm_device_t *lmdevice;

        lmdevice = &(umdevice->lm_dev);

        offset = lmdevice->hw_info.shmem_base;
        offset += OFFSETOF(shmem_region_t, drv_fw_mb.drv_pulse_mb);

        mutex_enter(&umdevice->os_param.ind_mutex);

        lmdevice->vars.drv_pulse_wr_seq++;

        msg_code = lmdevice->vars.drv_pulse_wr_seq & DRV_PULSE_SEQ_MASK;

        mutex_exit(&umdevice->os_param.ind_mutex);

        if (lmdevice->params.test_mode & TEST_MODE_DRIVER_PULSE_ALWAYS_ALIVE) {
                msg_code |= DRV_PULSE_ALWAYS_ALIVE;
        }

        REG_WR_IND(lmdevice, offset, msg_code);
}