/*
 * Copyright (c) 2009-2011, Broadcom Corporation
 * Copyright (c) 2014, QLogic Corporation
 *
 * Written by:  Benjamin Li  (benli@broadcom.com)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Adam Dunkels.
 * 4. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * bnx2.c - bnx2 user space driver
 *
 */
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <fcntl.h>
#include <unistd.h>

#include "config.h"

#include "build_date.h"
#include "bnx2.h"
#include "cnic.h"
#include "logger.h"
#include "nic.h"
#include "nic_utils.h"
#include "options.h"

#define PFX	"bnx2 "

/*  Foward struct declarations */
struct nic_ops bnx2_op;

/*******************************************************************************
 * NIC Library Strings
 ******************************************************************************/
static const char library_name[] = "bnx2";
static const char library_version[] = PACKAGE_VERSION;
static const char library_uio_name[] = "bnx2_cnic";

/*  The name that should be returned from /sys/class/uio/uio0/name */
static const char cnic_uio_sysfs_name_tempate[] = "/sys/class/uio/uio%i/name";
static const char cnic_uio_sysfs_name[] = "bnx2_cnic";

/*******************************************************************************
 * String constants used to display human readable adapter name
 ******************************************************************************/
static const char brcm_5706C[] = "QLogic NetXtreme II BCM5706 1000Base-T";
static const char hp_NC370T[] =
	"HP NC370T Multifunction Gigabit Server Adapter";
static const char hp_NC370I[] =
	"HP NC370i Multifunction Gigabit Server Adapter";
static const char brcm_5706S[] = "QLogic NetXtreme II BCM5706 1000Base-SX";
static const char hp_NC370F[] =
	"HP NC370F Multifunction Gigabit Server Adapter";
static const char brcm_5708C[] = "QLogic NetXtreme II BCM5708 1000Base-T";
static const char brcm_5708S[] = "QLogic NetXtreme II BCM5708 1000Base-SX";
static const char brcm_5709C[] = "QLogic NetXtreme II BCM5709 1000Base-T";
static const char brcm_5709S[] = "QLogic NetXtreme II BCM5709 1000Base-SX";
static const char brcm_5716C[] = "QLogic NetXtreme II BCM5716 1000Base-T";
static const char brcm_5716S[] = "QLogic NetXtreme II BCM5716 1000Base-SX";

/*******************************************************************************
 * PCI ID constants
 ******************************************************************************/
#define PCI_VENDOR_ID_BROADCOM          0x14e4
#define PCI_DEVICE_ID_NX2_5709          0x1639
#define PCI_DEVICE_ID_NX2_5709S         0x163a
#define PCI_DEVICE_ID_NX2_5706          0x164a
#define PCI_DEVICE_ID_NX2_5708          0x164c
#define PCI_DEVICE_ID_NX2_5706S         0x16aa
#define PCI_DEVICE_ID_NX2_5708S         0x16ac

#define PCI_VENDOR_ID_HP                0x103c

#define PCI_ANY_ID (~0)

/*  This is the table used to match PCI vendor and device ID's to the
 *  human readable string names of the devices */
static const struct pci_device_id bnx2_pci_tbl[] = {
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706,
	 PCI_VENDOR_ID_HP, 0x3101, hp_NC370T},
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706,
	 PCI_VENDOR_ID_HP, 0x3106, hp_NC370I},
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706,
	 PCI_ANY_ID, PCI_ANY_ID, brcm_5706S},
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5708,
	 PCI_ANY_ID, PCI_ANY_ID, brcm_5708C},
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706S,
	 PCI_VENDOR_ID_HP, 0x3102, hp_NC370F},
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706S,
	 PCI_ANY_ID, PCI_ANY_ID, brcm_5706S},
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5708S,
	 PCI_ANY_ID, PCI_ANY_ID, brcm_5708S},
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5709,
	 PCI_ANY_ID, PCI_ANY_ID, brcm_5709C},
	{PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5709S,
	 PCI_ANY_ID, PCI_ANY_ID, brcm_5709S},
	{PCI_VENDOR_ID_BROADCOM, 0x163b,
	 PCI_ANY_ID, PCI_ANY_ID, brcm_5716C},
	{PCI_VENDOR_ID_BROADCOM, 0x163c,
	 PCI_ANY_ID, PCI_ANY_ID, brcm_5716S},
};

/*******************************************************************************
 * bnx2 Library Functions
 ******************************************************************************/
/**
 *  bnx2_get_library_name() - Used to get the name of this NIC libary
 *  @param name - This function will return the pointer to this NIC
 *                library name
 *  @param name_size
 */
static void bnx2_get_library_name(char **name, size_t *name_size)
{
	*name = (char *)library_name;
	*name_size = sizeof(library_name);
}

/**
 *  bnx2_get_library_version() - Used to get the version string of this
 *                               NIC libary
 *  @param version - This function will return the pointer to this NIC
 *                   library version string
 *  @param version_size - This will be set with the version size
 */
static void bnx2_get_library_version(char **version, size_t *version_size)
{
	*version = (char *)library_version;
	*version_size = sizeof(library_version);
}

/**
 *  bnx2_get_build_date() - Used to get the build date string of this library
 *  @param version - This function will return the pointer to this NIC
 *                   library build date string
 *  @param version_size - This will be set with the build date string size
 */
static void bnx2_get_build_date(char **build, size_t *build_size)
{
	*build = (char *)build_date;
	*build_size = sizeof(build_date);
}

/**
 *  bnx2_get_transport_name() - Used to get the transport name associated
 *                              with this this NIC libary
 *  @param transport_name - This function will return the pointer to this NIC
 *                          library's associated transport string
 *  @param transport_name_size - This will be set with the transport name size
 */
static void bnx2_get_transport_name(char **transport_name,
				    size_t *transport_name_size)
{
	*transport_name = (char *)bnx2i_library_transport_name;
	*transport_name_size = bnx2i_library_transport_name_size;
}

/**
 *  bnx2_get_uio_name() - Used to get the uio name associated with this this
 *                        NIC libary
 *  @param uio_name - This function will return the pointer to this NIC
 *                    library's associated uio string
 *  @param transport_name_size - This will be set with the uio name size
 */
static void bnx2_get_uio_name(char **uio_name, size_t *uio_name_size)
{
	*uio_name = (char *)library_uio_name;
	*uio_name_size = sizeof(library_uio_name);
}

/**
 *  bnx2_get_pci_table() - Used to get the PCI table for this NIC libary
 *			   to determine which NIC's based off of PCI ID's
 *			   are supported
 *  @param table - This function will return the pointer to the PCI table
 *  @param entries - This function will return the number of entries in the NIC
 *                   library's PCI table
 */
static void bnx2_get_pci_table(struct pci_device_id **table, uint32_t *entries)
{
	*table = (struct pci_device_id *)bnx2_pci_tbl;
	*entries = (uint32_t) (sizeof(bnx2_pci_tbl) / sizeof(bnx2_pci_tbl[0]));
}

/**
 *  bnx2_get_ops() - Used to get the NIC library op table
 *  @param op - The op table of this NIC library
 */
struct nic_ops *bnx2_get_ops()
{
	return &bnx2_op;
}

/*******************************************************************************
 * bnx2 Utility Functions
 ******************************************************************************/
/*******************************************************************************
 * Utility Functions Used to read register from the bnx2 device
 ******************************************************************************/
static void bnx2_wr32(bnx2_t *bp, __u32 off, __u32 val)
{
	*((volatile __u32 *)(bp->reg + off)) = val;
}

static void bnx2_wr16(bnx2_t *bp, __u32 off, __u16 val)
{
	*((volatile __u16 *)(bp->reg + off)) = val;
}

static __u32 bnx2_rd32(bnx2_t *bp, __u32 off)
{
	return *((volatile __u32 *)(bp->reg + off));
}

static int bnx2_reg_sync(bnx2_t *bp, __u32 off, __u16 length)
{
	return msync(bp->reg + off, length, MS_SYNC);
}

/**
 * bnx2_get_chip_id() - Used to retrive the chip ID from the nic
 * @param dev - Device used to determin NIC type
 * @return Chip ID read from the MISC ID register
 */
static int bnx2_get_chip_id(bnx2_t *bp)
{
	return bnx2_rd32(bp, BNX2_MISC_ID);
}

/**
 *  bnx2_uio_verify()
 *
 */
static int bnx2_uio_verify(nic_t *nic)
{
	char *raw = NULL, *raw_tmp;
	uint32_t raw_size = 0;
	char temp_path[sizeof(cnic_uio_sysfs_name_tempate) + 8];
	int rc = 0;

	/*  Build the path to determine uio name */
	snprintf(temp_path, sizeof(temp_path),
		 cnic_uio_sysfs_name_tempate, nic->uio_minor);

	rc = capture_file(&raw, &raw_size, temp_path);
	if (rc != 0)
		goto error;

	/* sanitize name string by replacing newline with null termination */
	raw_tmp = raw;
	while (*raw_tmp != '\n')
		raw_tmp++;
	*raw_tmp = '\0';

	if (strncmp(raw, cnic_uio_sysfs_name, sizeof(cnic_uio_sysfs_name)) !=
	    0) {
		LOG_ERR(PFX "%s: uio names not equal: "
			"expecting %s got %s from %s",
			nic->log_name, cnic_uio_sysfs_name, raw, temp_path);
		rc = -EIO;
	}

	free(raw);

	LOG_INFO(PFX "%s: Verified is a cnic_uio device", nic->log_name);

error:
	return rc;
}

/*******************************************************************************
 * bnx2 Utility Functions to get to the hardware consumer indexes
 ******************************************************************************/
static __u16 bnx2_get_rx_msix(bnx2_t *bp)
{
	struct status_block_msix *sblk = bp->status_blk.msix;
	__u16 rx_cons;

	msync(sblk, sizeof(*sblk), MS_SYNC);
	rx_cons = sblk->status_rx_quick_consumer_index;
	barrier();
	if ((rx_cons & (MAX_RX_DESC_CNT)) == (MAX_RX_DESC_CNT))
		rx_cons++;

	return rx_cons;
}

static __u16 bnx2_get_rx_msi(bnx2_t *bp)
{
	struct status_block *sblk = bp->status_blk.msi;
	__u16 rx_cons;

	msync(sblk, sizeof(*sblk), MS_SYNC);
	rx_cons = BNX2_SBLK_EVEN_IDX(sblk->rx2);
	barrier();
	if ((rx_cons & (MAX_RX_DESC_CNT)) == (MAX_RX_DESC_CNT))
		rx_cons++;

	return rx_cons;
}

static __u16 bnx2_get_tx_msix(bnx2_t *bp)
{
	struct status_block_msix *sblk = bp->status_blk.msix;
	__u16 tx_cons;

	msync(sblk, sizeof(*sblk), MS_SYNC);
	tx_cons = sblk->status_tx_quick_consumer_index;
	barrier();
	if ((tx_cons & (MAX_TX_DESC_CNT)) == (MAX_TX_DESC_CNT))
		tx_cons++;

	return tx_cons;
}

static __u16 bnx2_get_tx_msi(bnx2_t *bp)
{
	struct status_block *sblk = bp->status_blk.msi;
	__u16 tx_cons;

	msync(sblk, sizeof(*sblk), MS_SYNC);
	tx_cons = BNX2_SBLK_EVEN_IDX(sblk->tx2);
	barrier();
	if ((tx_cons & (MAX_TX_DESC_CNT)) == (MAX_TX_DESC_CNT))
		tx_cons++;

	return tx_cons;
}

typedef enum {
	CNIC_VLAN_STRIPPING_ENABLED = 1,
	CNIC_VLAN_STRIPPING_DISABLED = 2,
} CNIC_VLAN_STRIPPING_MODE;

/**
 *  bnx2_strip_vlan_enabled() - This will query the device to determine whether
 *                              VLAN tag stripping is enabled or not
 *  @param dev - device to check stripping or not
 *  @ return CNIC_VLAN_STRIPPING_ENABLED stripping is enabled
 *           CNIC_VLAN_STRIPPING_DISABLED stripping is not enabled
 */
static CNIC_VLAN_STRIPPING_MODE bnx2_strip_vlan_enabled(bnx2_t *bp)
{
	uint32_t val;

	val = bnx2_rd32(bp, BNX2_EMAC_RX_MODE);

	if (val & BNX2_EMAC_RX_MODE_KEEP_VLAN_TAG)
		return CNIC_VLAN_STRIPPING_DISABLED;
	else
		return CNIC_VLAN_STRIPPING_ENABLED;
}

/**
 *  bnx2_free() - Used to free a bnx2 structure
 */
static void bnx2_free(nic_t *nic)
{
	if (nic->priv)
		free(nic->priv);
	nic->priv = NULL;
}


/**
 *  bnx2_alloc() - Used to allocate a bnx2 structure
 */
static bnx2_t *bnx2_alloc(nic_t *nic)
{
	bnx2_t *bp = malloc(sizeof(*bp));
	if (bp == NULL) {
		LOG_ERR(PFX "%s: Could not allocate bnx2 space", nic->log_name);
		return NULL;
	}

	/*  Clear out the bnx2 contents */
	memset(bp, 0, sizeof(*bp));

	bp->bar0_fd = INVALID_FD;
	bp->flags = BNX2_UIO_TX_HAS_SENT;

	bp->parent = nic;
	nic->priv = (void *)bp;

	return bp;
}

/**
 * bnx2_open() - This will initialize all the hardware resources
 * @param dev - The struct nic device to open
 * @return 0 on success, on failure a errno will be returned
 */
static int bnx2_open(nic_t *nic)
{
	bnx2_t *bp;
	struct stat uio_stat;
	int i, rc;
	__u32 val;
	uint32_t tx_cid;
	__u32 msix_vector = 0;
	char sysfs_resc_path[80];

	/*  Sanity Check: validate the parameters */
	if (nic == NULL) {
		LOG_ERR(PFX "bnx2_open(): nic == NULL");
		return -EINVAL;
	}

	if ((nic->priv) != NULL &&
	    (((bnx2_t *) (nic->priv))->flags & BNX2_OPENED)) {
		return 0;
	}

	bp = bnx2_alloc(nic);
	if (bp == NULL) {
		LOG_ERR(PFX "bnx2_open(): Couldn't allocate bp priv struct",
			nic->log_name);
		return -ENOMEM;
	}

	while (nic->fd < 0) {
		nic->fd = open(nic->uio_device_name, O_RDWR | O_NONBLOCK);
		if (nic->fd != INVALID_FD) {
			LOG_ERR(PFX
				"%s: uio device has been brought up via pid: "
				"%d on fd: %d",
				nic->uio_device_name, getpid(), nic->fd);

			rc = bnx2_uio_verify(nic);
			if (rc != 0)
				continue;

			break;
		} else {
			LOG_WARN(PFX "%s: Could not open device: %s, [%s]",
				 nic->log_name, nic->uio_device_name,
				 strerror(errno));
			manually_trigger_uio_event(nic, nic->uio_minor);

			/*  udev might not have created the file yet */
			pthread_mutex_unlock(&nic->nic_mutex);
			sleep(1);
			pthread_mutex_lock(&nic->nic_mutex);
		}
	}
	if (fstat(nic->fd, &uio_stat) < 0) {
		LOG_ERR(PFX "%s: Could not fstat device", nic->log_name);
		errno = -ENODEV;
		goto error_alloc_rx_ring;
	}
	nic->uio_minor = minor(uio_stat.st_rdev);

	cnic_get_sysfs_pci_resource_path(nic, 0, sysfs_resc_path, 80);
	bp->bar0_fd = open(sysfs_resc_path, O_RDWR | O_SYNC);
	if (bp->bar0_fd < 0) {
		LOG_ERR(PFX "%s: Could not open %s", nic->log_name,
			sysfs_resc_path);
		errno = -ENODEV;
		goto error_alloc_rx_ring;
	}

	/*  TODO: hardcoded with the cnic driver */
	bp->rx_ring_size = 3;
	bp->rx_buffer_size = 0x400;

	LOG_DEBUG(PFX "%s: using rx ring size: %d, rx buffer size: %d",
		  nic->log_name, bp->rx_ring_size, bp->rx_buffer_size);

	/*  Determine the number of UIO events that have already occured */
	rc = detemine_initial_uio_events(nic, &nic->intr_count);
	if (rc != 0) {
		LOG_ERR("Could not determine the number ofinitial UIO events");
		nic->intr_count = 0;
	}

	/*  Allocate space for rx ring pointer */
	bp->rx_ring = malloc(sizeof(struct l2_fhdr *) * bp->rx_ring_size);
	if (bp->rx_ring == NULL) {
		LOG_ERR(PFX "%s: Could not allocate space for rx_ring",
			nic->log_name);
		errno = -ENOMEM;
		goto error_alloc_rx_ring;
	}
	mlock(bp->rx_ring, sizeof(struct l2_fhdr *) * bp->rx_ring_size);

	/*  Allocate space for rx pkt ring */
	bp->rx_pkt_ring = malloc(sizeof(void *) * bp->rx_ring_size);
	if (bp->rx_pkt_ring == NULL) {
		LOG_ERR(PFX "%s: Could not allocate space for rx_pkt_ring",
			nic->log_name);
		errno = -ENOMEM;
		goto error_alloc_rx_pkt_ring;
	}
	mlock(bp->rx_pkt_ring, sizeof(void *) * bp->rx_ring_size);

	bp->reg = mmap(NULL, 0x12800, PROT_READ | PROT_WRITE, MAP_SHARED,
		       bp->bar0_fd, (off_t) 0);
	if (bp->reg == MAP_FAILED) {
		LOG_INFO(PFX "%s: Couldn't mmap registers: %s",
			 nic->log_name, strerror(errno));
		bp->reg = NULL;
		goto error_regs;
	}

	msync(bp->reg, 0x12800, MS_SYNC);
	LOG_DEBUG(PFX "Chip ID: %x", bnx2_get_chip_id(bp));

	/*  on a 5709 when using MSI-X the status block is at an offset */
	if (BNX2_CHIP_NUM(bnx2_get_chip_id(bp)) == CHIP_NUM_5709) {
		/*  determine if we are using MSI-X */
		val = bnx2_rd32(bp, BNX2_TSCH_TSS_CFG);
		if (val) {
			/*  We are in MSI-X mode */
			uint32_t base_cid = ((val >> 10) & 0x7ff) << 3;
			msix_vector = (val >> 24) & 0xf;

			bp->status_blk_size = (128 * 9);

			tx_cid = base_cid + msix_vector - 1;
			bp->flags |= BNX2_UIO_MSIX_ENABLED;

			bp->get_tx_cons = bnx2_get_tx_msix;
			bp->get_rx_cons = bnx2_get_rx_msix;

			LOG_DEBUG(PFX "%s: tss_cfg: 0x%x tx cid: %d",
				  nic->log_name, val, tx_cid);

			LOG_INFO(PFX "%s: detected using MSI-X vector: %d",
				 nic->log_name, msix_vector);
		} else {
			/*  We are not in MSI-X mode */
			bp->status_blk_size = 64;
			tx_cid = 20;

			bp->get_tx_cons = bnx2_get_tx_msi;
			bp->get_rx_cons = bnx2_get_rx_msi;
		}
	} else {
		bp->status_blk_size = 64;
		tx_cid = 20;

		bp->get_tx_cons = bnx2_get_tx_msi;
		bp->get_rx_cons = bnx2_get_rx_msi;
	}

	bp->sblk_map = mmap(NULL, bp->status_blk_size,
			    PROT_READ | PROT_WRITE, MAP_SHARED,
			    nic->fd, (off_t) nic->page_size);
	if (bp->sblk_map == MAP_FAILED) {
		LOG_INFO(PFX "%s: Could not mmap status block: %s",
			 nic->log_name, strerror(errno));
		goto error_sblk;
	}

	if (bp->flags & BNX2_UIO_MSIX_ENABLED) {
		uint8_t *status_blk = (uint8_t *) bp->sblk_map;
		status_blk += (msix_vector * 128);

		bp->status_blk.msix = (struct status_block_msix *)status_blk;

		LOG_DEBUG(PFX "%s: msix initial cons: tx:%d rx:%d",
			  nic->log_name,
			  bp->status_blk.msix->status_tx_quick_consumer_index,
			  bp->status_blk.msix->status_rx_quick_consumer_index);
	} else {
		bp->status_blk.msi = (struct status_block *)bp->sblk_map;

		LOG_DEBUG(PFX "%s: msi initial tx:%d rx:%d",
			  nic->log_name,
			  BNX2_SBLK_EVEN_IDX(bp->status_blk.msi->tx2),
			  BNX2_SBLK_EVEN_IDX(bp->status_blk.msi->rx2));
	}

	bp->tx_ring = mmap(NULL, 2 * nic->page_size,
			   PROT_READ | PROT_WRITE, MAP_SHARED, nic->fd,
			   (off_t) 2 * nic->page_size);
	if (bp->tx_ring == MAP_FAILED) {
		LOG_INFO(PFX "%s: Could not mmap tx ring: %s",
			 nic->log_name, strerror(errno));
		bp->tx_ring = NULL;
		goto error_tx_ring;
	}

	bp->bufs = mmap(NULL, (bp->rx_ring_size + 1) * bp->rx_buffer_size,
			PROT_READ | PROT_WRITE,
			MAP_SHARED, nic->fd, (off_t) 3 * nic->page_size);
	if (bp->bufs == MAP_FAILED) {
		LOG_INFO(PFX "%s: Could not mmap buffers: %s",
			 nic->log_name, strerror(errno));
		bp->bufs = NULL;
		goto error_bufs;
	}

	bp->tx_bidx_io = MB_GET_CID_ADDR(tx_cid) + BNX2_L2CTX_TX_HOST_BIDX;
	bp->tx_bseq_io = MB_GET_CID_ADDR(tx_cid) + BNX2_L2CTX_TX_HOST_BSEQ;
	LOG_INFO(PFX "%s: tx_bidx_io: 0x%x tx_bseq_io: 0x%x",
		 nic->log_name, bp->tx_bidx_io, bp->tx_bseq_io);

	bp->rx_bidx_io = MB_GET_CID_ADDR(2) + BNX2_L2CTX_HOST_BDIDX;
	bp->rx_bseq_io = MB_GET_CID_ADDR(2) + BNX2_L2CTX_HOST_BSEQ;

	bp->tx_cons = 0;
	bp->tx_prod = 0;
	bp->tx_pkt = bp->bufs;

	bp->rx_index = 0;
	bp->rx_cons = 0;
	bp->rx_prod = bp->rx_ring_size;
	bp->rx_bseq = bp->rx_prod * bp->rx_buffer_size;
	bnx2_wr16(bp, bp->rx_bidx_io, bp->rx_prod);
	bnx2_wr32(bp, bp->rx_bseq_io, bp->rx_bseq);

	bnx2_reg_sync(bp, bp->rx_bidx_io, sizeof(__u16));
	bnx2_reg_sync(bp, bp->rx_bseq_io, sizeof(__u32));

	for (i = 0; i < bp->rx_ring_size; i++) {
		void *ptr = bp->bufs + (bp->rx_buffer_size * (i + 1));

		bp->rx_ring[i] = (struct l2_fhdr *)ptr;
		bp->rx_pkt_ring[i] = ptr + sizeof(struct l2_fhdr) + 2;
	}

	/*  Read the MAC address used for the iSCSI interface */
	val = bnx2_rd32(bp, BNX2_EMAC_MAC_MATCH4);
	nic->mac_addr[0] = (__u8) (val >> 8);
	nic->mac_addr[1] = (__u8) val;

	val = bnx2_rd32(bp, BNX2_EMAC_MAC_MATCH5);
	nic->mac_addr[2] = (__u8) (val >> 24);
	nic->mac_addr[3] = (__u8) (val >> 16);
	nic->mac_addr[4] = (__u8) (val >> 8);
	nic->mac_addr[5] = (__u8) val;

	LOG_INFO(PFX "%s:  Using mac address: %2x:%2x:%2x:%2x:%2x:%2x",
		 nic->log_name,
		 nic->mac_addr[0], nic->mac_addr[1], nic->mac_addr[2],
		 nic->mac_addr[3], nic->mac_addr[4], nic->mac_addr[5]);

	/*  Determine if Hardware VLAN tag stripping is enabled or not */
	if (CNIC_VLAN_STRIPPING_ENABLED == bnx2_strip_vlan_enabled(bp))
		nic->flags |= NIC_VLAN_STRIP_ENABLED;

	/*  Prepare the multicast addresses */
	val = 4 | BNX2_RPM_SORT_USER2_BC_EN | BNX2_RPM_SORT_USER2_MC_EN;
	if (BNX2_CHIP_NUM(bnx2_get_chip_id(bp)) != CHIP_NUM_5709)
		val |= BNX2_RPM_SORT_USER2_PROM_VLAN;

	bnx2_wr32(bp, BNX2_RPM_SORT_USER2, 0x0);
	bnx2_wr32(bp, BNX2_RPM_SORT_USER2, val);
	bnx2_wr32(bp, BNX2_RPM_SORT_USER2, val | BNX2_RPM_SORT_USER2_ENA);

	rc = enable_multicast(nic);
	if (rc != 0) {
		errno = rc;
		goto error_bufs;
	}
	msync(bp->reg, 0x12800, MS_SYNC);
	LOG_INFO("%s: bnx2 uio initialized", nic->log_name);

	bp->flags |= BNX2_OPENED;

	return 0;

error_bufs:
	munmap(bp->tx_ring, 2 * nic->page_size);

error_tx_ring:
	munmap(bp->status_blk.msi, bp->status_blk_size);

error_sblk:
	munmap(bp->reg, 0x12800);

error_regs:
	munlock(bp->rx_pkt_ring, sizeof(void *) * bp->rx_ring_size);
	free(bp->rx_pkt_ring);
	bp->rx_pkt_ring = NULL;

error_alloc_rx_pkt_ring:
	munlock(bp->rx_ring, sizeof(struct l2_fhdr *) * bp->rx_ring_size);
	free(bp->rx_ring);
	bp->rx_ring = NULL;

error_alloc_rx_ring:
	if (nic->fd != INVALID_FD) {
		close(nic->fd);
		nic->fd = INVALID_FD;
	}
	bnx2_free(nic);

	return errno;
}

/**
 *  bnx2_uio_close_resources() - Used to free resource for the bnx2 NIC
 *  @param nic - NIC device to free resource
 *  @param graceful - whether to wait to close gracefully
 *  @return 0 on success, <0 on failure
 */
static int bnx2_uio_close_resources(nic_t *nic, NIC_SHUTDOWN_T graceful)
{
	bnx2_t *bp = (bnx2_t *) nic->priv;
	int rc = 0;

	/*  Remove the multicast addresses if added */
	if ((nic->flags & NIC_ADDED_MULICAST) &&
	    (graceful == ALLOW_GRACEFUL_SHUTDOWN))
		disable_multicast(nic);

	/*  Check if there is an assoicated bnx2 device */
	if (bp == NULL) {
		LOG_WARN(PFX "%s: when closing resources there is "
			 "no assoicated bnx2", nic->log_name);
		return -EIO;
	}

	/*  Clean up allocated memory */
	if (bp->rx_ring != NULL) {
		free(bp->rx_ring);
		bp->rx_ring = NULL;
	}

	if (bp->rx_pkt_ring != NULL) {
		free(bp->rx_pkt_ring);
		bp->rx_pkt_ring = NULL;
	}

	/*  Clean up mapped registers */
	if (bp->bufs != NULL) {
		rc = munmap(bp->bufs,
			    (bp->rx_ring_size + 1) * bp->rx_buffer_size);
		if (rc != 0)
			LOG_WARN(PFX "%s: Couldn't unmap bufs", nic->log_name);
		bp->bufs = NULL;
	}

	if (bp->tx_ring != NULL) {
		rc = munmap(bp->tx_ring, 2 * nic->page_size);
		if (rc != 0)
			LOG_WARN(PFX "%s: Couldn't unmap tx_rings",
				 nic->log_name);
		bp->tx_ring = NULL;
	}

	if (bp->status_blk.msix != NULL || bp->status_blk.msi != NULL) {
		rc = munmap(bp->sblk_map, bp->status_blk_size);
		if (rc != 0)
			LOG_WARN(PFX "%s: Couldn't unmap status block",
				 nic->log_name);
		bp->sblk_map = NULL;

		bp->status_blk.msix = NULL;
		bp->status_blk.msi = NULL;
	}

	if (bp->reg != NULL) {
		rc = munmap(bp->reg, 0x12800);
		if (rc != 0)
			LOG_WARN(PFX "%s: Couldn't unmap regs", nic->log_name);
		bp->reg = NULL;
	}

	if (bp->bar0_fd != INVALID_FD) {
		close(bp->bar0_fd);
		bp->bar0_fd = INVALID_FD;
	}

	if (nic->fd != INVALID_FD) {
		rc = close(nic->fd);
		if (rc != 0) {
			LOG_WARN(PFX
				 "%s: Couldn't close uio file descriptor: %d",
				 nic->log_name, nic->fd);
		} else {
			LOG_DEBUG(PFX "%s: Closed uio file descriptor: %d",
				  nic->log_name, nic->fd);
		}

		nic->fd = INVALID_FD;
	} else {
		LOG_WARN(PFX "%s: Invalid uio file descriptor: %d",
			 nic->log_name, nic->fd);
	}

	LOG_INFO(PFX "%s: Closed all resources", nic->log_name);

	return 0;
}

/**
 *  bnx2_close() - Used to close the NIC device
 *  @param nic - NIC device to close
 *  @param graceful - whether to wait to close gracefully
 *  @return 0 if successful, <0 if there is an error
 */
static int bnx2_close(nic_t *nic, NIC_SHUTDOWN_T graceful)
{
	/*  Sanity Check: validate the parameters */
	if (nic == NULL) {
		LOG_ERR(PFX "bnx2_close(): nic == NULL");
		return -EINVAL;
	}

	LOG_INFO(PFX "Closing NIC device: %s", nic->log_name);

	bnx2_uio_close_resources(nic, graceful);
	bnx2_free(nic);

	return 0;
}

static void bnx2_prepare_xmit_packet(nic_t *nic,
				     nic_interface_t *nic_iface,
				     struct packet *pkt)
{
	bnx2_t *bp = (bnx2_t *) nic->priv;
	struct uip_vlan_eth_hdr *eth_vlan = (struct uip_vlan_eth_hdr *)pkt->buf;
	struct uip_eth_hdr *eth = (struct uip_eth_hdr *)bp->tx_pkt;

	if (eth_vlan->tpid == htons(UIP_ETHTYPE_8021Q)) {
		memcpy(bp->tx_pkt, pkt->buf, sizeof(struct uip_eth_hdr));
		eth->type = eth_vlan->type;
		pkt->buf_size -= (sizeof(struct uip_vlan_eth_hdr) -
				  sizeof(struct uip_eth_hdr));
		memcpy(bp->tx_pkt + sizeof(struct uip_eth_hdr),
		       pkt->buf + sizeof(struct uip_vlan_eth_hdr),
		       pkt->buf_size - sizeof(struct uip_eth_hdr));
	} else
		memcpy(bp->tx_pkt, pkt->buf, pkt->buf_size);

	msync(bp->tx_pkt, pkt->buf_size, MS_SYNC);
}

/**
 *  bnx2_get_tx_pkt() - This function is used to a TX packet from the NIC
 *  @param nic - The NIC device to send the packet
 *
 */
void *bnx2_get_tx_pkt(nic_t *nic)
{
	bnx2_t *bp = (bnx2_t *) nic->priv;
	return bp->tx_pkt;
}

/**
 *  bnx2_start_xmit() - This function is used to send a packet of data
 *  @param nic - The NIC device to send the packet
 *  @param len - the length of the TX packet
 *
 */
void bnx2_start_xmit(nic_t *nic, size_t len, u16_t vlan_id)
{
	bnx2_t *bp = (bnx2_t *) nic->priv;
	uint16_t ring_prod;
	struct tx_bd *txbd;
	struct rx_bd *rxbd;
	rxbd = (struct rx_bd *)(((__u8 *) bp->tx_ring) + nic->page_size);

	if ((rxbd->rx_bd_haddr_hi == 0) && (rxbd->rx_bd_haddr_lo == 0)) {
		LOG_PACKET(PFX "%s: trying to transmit when device is closed",
			   nic->log_name);
		pthread_mutex_unlock(&nic->xmit_mutex);
		return;
	}

	ring_prod = TX_RING_IDX(bp->tx_prod);
	txbd = &bp->tx_ring[ring_prod];

	txbd->tx_bd_mss_nbytes = len;

	if (vlan_id) {
		txbd->tx_bd_vlan_tag_flags = (vlan_id << 16) |
		    TX_BD_FLAGS_VLAN_TAG | TX_BD_FLAGS_END | TX_BD_FLAGS_START;
	} else
		txbd->tx_bd_vlan_tag_flags = TX_BD_FLAGS_END |
		    TX_BD_FLAGS_START;

	bp->tx_bseq += len;
	bp->tx_prod = NEXT_TX_BD(bp->tx_prod);

	bnx2_wr16(bp, bp->tx_bidx_io, bp->tx_prod);
	bnx2_wr32(bp, bp->tx_bseq_io, bp->tx_bseq);

	bnx2_reg_sync(bp, bp->tx_bidx_io, sizeof(__u16));
	bnx2_reg_sync(bp, bp->tx_bseq_io, sizeof(__u32));

	LOG_PACKET(PFX "%s: sent %d bytes using dev->tx_prod: %d",
		   nic->log_name, len, bp->tx_prod);
}

/**
 *  bnx2_write() - Used to write the data to the hardware
 *  @param nic - NIC hardware to read from
 *  @param pkt - The packet which will hold the data to be sent on the wire
 *  @return 0 if successful, <0 if failed
 */
int bnx2_write(nic_t *nic, nic_interface_t *nic_iface, packet_t *pkt)
{
	bnx2_t *bp;
	struct uip_stack *uip;

	/* Sanity Check: validate the parameters */
	if (nic == NULL || nic_iface == NULL || pkt == NULL) {
		LOG_ERR(PFX "%s: bnx2_write() nic == 0x%p || "
			" nic_iface == 0x%p || "
			" pkt == 0x%x", nic, nic_iface, pkt);
		return -EINVAL;
	}
	bp = (bnx2_t *)nic->priv;
	uip = &nic_iface->ustack;

	if (pkt->buf_size == 0) {
		LOG_ERR(PFX "%s: Trying to transmitted 0 sized packet",
			nic->log_name);
		return -EINVAL;
	}

	if (pthread_mutex_trylock(&nic->xmit_mutex) != 0) {
		LOG_PACKET(PFX "%s: Dropped previous transmitted packet",
			   nic->log_name);
		return -EINVAL;
	}

	bnx2_prepare_xmit_packet(nic, nic_iface, pkt);
	bnx2_start_xmit(nic, pkt->buf_size,
			(nic_iface->vlan_priority << 12) |
			nic_iface->vlan_id);

	/*  bump the bnx2 dev send statistics */
	nic->stats.tx.packets++;
	nic->stats.tx.bytes += uip->uip_len;

	LOG_PACKET(PFX "%s: transmitted %d bytes "
		   "dev->tx_cons: %d, dev->tx_prod: %d, dev->tx_bseq:%d",
		   nic->log_name, pkt->buf_size,
		   bp->tx_cons, bp->tx_prod, bp->tx_bseq);

	return 0;
}

/**
 *  bnx2_read() - Used to read the data from the hardware
 *  @param nic - NIC hardware to read from
 *  @param pkt - The packet which will hold the data
 *  @return 0 if successful, <0 if failed
 */
static int bnx2_read(nic_t *nic, packet_t *pkt)
{
	bnx2_t *bp;
	int rc = 0;
	uint16_t hw_cons, sw_cons;

	/* Sanity Check: validate the parameters */
	if (unlikely(nic == NULL || pkt == NULL)) {
		LOG_ERR(PFX "%s: bnx2_write() nic == 0x%p || "
			" pkt == 0x%x", nic, pkt);
		return -EINVAL;
	}
	bp = (bnx2_t *)nic->priv;

	hw_cons = bp->get_rx_cons(bp);
	sw_cons = bp->rx_cons;

	if (sw_cons != hw_cons) {
		uint8_t rx_index = bp->rx_index % 3;
		struct l2_fhdr *rx_hdr = bp->rx_ring[rx_index];
		void *rx_pkt = bp->rx_pkt_ring[rx_index];
		int len;
		uint16_t errors;

		LOG_PACKET(PFX "%s: clearing rx interrupt: %d %d %d",
			   nic->log_name, sw_cons, hw_cons, rx_index);

		msync(rx_hdr, sizeof(struct l2_fhdr), MS_SYNC);
		errors = ((rx_hdr->l2_fhdr_status & 0xffff0000) >> 16);
		len = ((rx_hdr->l2_fhdr_vtag_len & 0xffff0000) >> 16) - 4;

		if (unlikely((errors & (L2_FHDR_ERRORS_BAD_CRC |
					L2_FHDR_ERRORS_PHY_DECODE |
					L2_FHDR_ERRORS_ALIGNMENT |
					L2_FHDR_ERRORS_TOO_SHORT |
					L2_FHDR_ERRORS_GIANT_FRAME)) ||
			     (len <= 0) ||
			     (len > (bp->rx_buffer_size -
				     (sizeof(struct l2_fhdr) + 2))) ||
			     (len > pkt->max_buf_size))) {
			/*  One of the fields in the BD is bad */
			uint16_t status = ((rx_hdr->l2_fhdr_status &
					    0x0000ffff));

			LOG_ERR(PFX "%s: Recv error: 0x%x status: 0x%x "
				"len: %d", nic->log_name, errors, status, len);

			if ((len < (bp->rx_buffer_size -
				    (sizeof(struct l2_fhdr) + 2))) &&
			    (len < pkt->max_buf_size))
				dump_packet_to_log(pkt->nic_iface, rx_pkt, len);
		} else {
			if (len < (bp->rx_buffer_size -
				   (sizeof(struct l2_fhdr) + 2))) {
				msync(rx_pkt, len, MS_SYNC);
				/*  Copy the data */
				memcpy(pkt->buf, rx_pkt, len);
				pkt->buf_size = len;

				/*  Properly set the packet flags */
				/*  check if there is VLAN tagging on the
				 *  packet */
				if (rx_hdr->l2_fhdr_status &
				    L2_FHDR_STATUS_VLAN_TAG) {
					pkt->vlan_tag =
					    rx_hdr->l2_fhdr_vtag_len & 0x0FFF;
					pkt->flags |= VLAN_TAGGED;
				} else {
					pkt->vlan_tag = 0;
				}

				rc = 1;

				LOG_PACKET(PFX "%s: processing packet "
					   "length: %d", nic->log_name, len);
			} else {
				/*  If the NIC passes up a packet bigger
				 *  then the RX buffer, flag it */
				LOG_ERR(PFX "%s: invalid packet length %d "
					"receive ", nic->log_name, len);
			}
		}

		bp->rx_index++;
		sw_cons = NEXT_RX_BD(sw_cons);
		bp->rx_prod = NEXT_RX_BD(bp->rx_prod);
		bp->rx_bseq += 0x400;

		bp->rx_cons = sw_cons;
		bnx2_wr16(bp, bp->rx_bidx_io, bp->rx_prod);
		bnx2_wr32(bp, bp->rx_bseq_io, bp->rx_bseq);

		bnx2_reg_sync(bp, bp->rx_bidx_io, sizeof(__u16));
		bnx2_reg_sync(bp, bp->rx_bseq_io, sizeof(__u32));

		/*  bump the bnx2 dev recv statistics */
		nic->stats.rx.packets++;
		nic->stats.rx.bytes += pkt->buf_size;
	}

	return rc;
}

/*******************************************************************************
 * Clearing TX interrupts
 ******************************************************************************/
/**
 *  bnx2_clear_tx_intr() - This routine is called when a TX interrupt occurs
 *  @param nic - the nic the interrupt occured on
 *  @return  0 on success
 */
static int bnx2_clear_tx_intr(nic_t *nic)
{
	bnx2_t *bp;
	uint16_t hw_cons;

	/* Sanity check: ensure the parameters passed in are valid */
	if (unlikely(nic == NULL)) {
		LOG_ERR(PFX "bnx2_read() nic == NULL");
		return -EINVAL;
	}
	bp = (bnx2_t *) nic->priv;
	hw_cons = bp->get_tx_cons(bp);

	if (bp->flags & BNX2_UIO_TX_HAS_SENT)
		bp->flags &= ~BNX2_UIO_TX_HAS_SENT;

	LOG_PACKET(PFX "%s: clearing tx interrupt [%d %d]",
		   nic->log_name, bp->tx_cons, hw_cons);

	bp->tx_cons = hw_cons;

	/*  There is a queued TX packet that needs to be sent out.  The usual
	 *  case is when stack will send an ARP packet out before sending the
	 *  intended packet */
	if (nic->tx_packet_queue != NULL) {
		packet_t *pkt;

		LOG_PACKET(PFX "%s: sending queued tx packet", nic->log_name);
		pkt = nic_dequeue_tx_packet(nic);

		/*  Got a TX packet buffer of the TX queue and put it onto
		 *  the hardware */
		if (pkt != NULL) {
			bnx2_prepare_xmit_packet(nic, pkt->nic_iface, pkt);

			bnx2_start_xmit(nic, pkt->buf_size,
					(pkt->nic_iface->vlan_priority << 12) |
					pkt->nic_iface->vlan_id);

			LOG_PACKET(PFX "%s: transmitted queued packet %d bytes "
				   "dev->tx_cons: %d, dev->tx_prod: %d, "
				   "dev->tx_bseq:%d",
				   nic->log_name, pkt->buf_size,
				   bp->tx_cons, bp->tx_prod, bp->tx_bseq);

			return -EAGAIN;
		}
	}

	pthread_mutex_unlock(&nic->xmit_mutex);

	return 0;
}

/*******************************************************************************
 * bnx2 NIC op's table
 ******************************************************************************/
struct nic_ops bnx2_op = {
	.description = "bnx2",
	.open = bnx2_open,
	.close = bnx2_close,
	.write = bnx2_write,
	.get_tx_pkt = bnx2_get_tx_pkt,
	.start_xmit = bnx2_start_xmit,
	.read = bnx2_read,
	.clear_tx_intr = bnx2_clear_tx_intr,
	.handle_iscsi_path_req = cnic_handle_iscsi_path_req,

	.lib_ops = {
		    .get_library_name = bnx2_get_library_name,
		    .get_pci_table = bnx2_get_pci_table,
		    .get_library_version = bnx2_get_library_version,
		    .get_build_date = bnx2_get_build_date,
		    .get_transport_name = bnx2_get_transport_name,
		    .get_uio_name = bnx2_get_uio_name,
		    },
};
