/*
 * vxge-main.c: iPXE driver for Neterion Inc's X3100 Series 10GbE
 *              PCIe I/O Virtualized Server Adapter.
 *
 * Copyright(c) 2002-2010 Neterion Inc.
 *
 * This software may be used and distributed according to the terms of
 * the GNU General Public License (GPL), incorporated herein by
 * reference.  Drivers based on or derived from this code fall under
 * the GPL and must retain the authorship, copyright and license
 * notice.
 *
 */

FILE_LICENCE(GPL2_ONLY);

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ipxe/io.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/pci.h>
#include <ipxe/malloc.h>
#include <ipxe/if_ether.h>
#include <ipxe/ethernet.h>
#include <ipxe/iobuf.h>
#include <ipxe/netdevice.h>
#include <ipxe/timer.h>
#include <nic.h>

#include "vxge_main.h"
#include "vxge_reg.h"

/* function modes strings */
static char *vxge_func_mode_names[] = {
	"Single Function - 1 func, 17 vpath",
	"Multi Function 8 - 8 func, 2 vpath per func",
	"SRIOV 17 - 17 VF, 1 vpath per VF",
	"WLPEX/SharedIO 17 - 17 VH, 1 vpath/func/hierarchy",
	"WLPEX/SharedIO 8 - 8 VH, 2 vpath/func/hierarchy",
	"Multi Function 17 - 17 func, 1 vpath per func",
	"SRIOV 8 - 1 PF, 7 VF, 2 vpath per VF",
	"SRIOV 4 - 1 PF, 3 VF, 4 vpath per VF",
	"Multi Function 2 - 2 func, 8 vpath per func",
	"Multi Function 4 - 4 func, 4 vpath per func",
	"WLPEX/SharedIO 4 - 17 func, 1 vpath per func (PCIe ARI)",
	"Multi Function 8 - For ESX DirectIO - 8 func, 2 vpath per func",
};

static inline int is_vxge_card_up(struct vxgedev *vdev)
{
	return test_bit(__VXGE_STATE_CARD_UP, vdev->state);
}

/*
 * vxge_xmit_compl
 *
 * If an interrupt was raised to indicate DMA complete of the Tx packet,
 * this function is called. It identifies the last TxD whose buffer was
 * freed and frees all skbs whose data have already DMA'ed into the NICs
 * internal memory.
 */
enum vxge_hw_status
vxge_xmit_compl(struct __vxge_hw_fifo *fifo_hw,
		struct vxge_hw_fifo_txd *txdp, enum vxge_hw_fifo_tcode tcode)
{
	struct net_device *netdev;
	struct io_buffer *tx_iob = NULL;

	vxge_trace();

	netdev = fifo_hw->vpathh->hldev->ndev;

	tx_iob = (struct io_buffer *)(intptr_t)txdp->host_control;

	if (tcode == VXGE_HW_FIFO_T_CODE_OK) {
		netdev_tx_complete(netdev, tx_iob);
	} else {
		netdev_tx_complete_err(netdev, tx_iob, -EINVAL);
		vxge_debug(VXGE_ERR, "%s: transmit failed, tcode %d\n",
				netdev->name, tcode);
	}

	memset(txdp, 0, sizeof(struct vxge_hw_fifo_txd));

	return VXGE_HW_OK;
}

/* reset vpaths */
enum vxge_hw_status vxge_reset_all_vpaths(struct vxgedev *vdev)
{
	enum vxge_hw_status status = VXGE_HW_OK;
	struct __vxge_hw_virtualpath *vpath;

	vxge_trace();

	vpath = vdev->vpath.vpathh;

	if (vpath) {
		if ((status = vxge_hw_vpath_reset(vpath)) == VXGE_HW_OK) {
			if (is_vxge_card_up(vdev) &&
				(status = vxge_hw_vpath_recover_from_reset(
					vpath))	!= VXGE_HW_OK) {
				vxge_debug(VXGE_ERR, "vxge_hw_vpath_recover_"
					"from_reset failed\n");
				return status;
			} else {
				status = __vxge_hw_vpath_reset_check(vpath);
				if (status != VXGE_HW_OK) {
					vxge_debug(VXGE_ERR,
					"__vxge_hw_vpath_reset_check error\n");
					return status;
				}
			}
		} else {
			vxge_debug(VXGE_ERR, "vxge_hw_vpath_reset failed\n");
			return status;
		}
	}
	return status;
}

/* close vpaths */
void vxge_close_vpaths(struct vxgedev *vdev)
{

	if (vdev->vpath.vpathh && vdev->vpath.is_open)
		vxge_hw_vpath_close(vdev->vpath.vpathh);

	vdev->vpath.is_open = 0;
	vdev->vpath.vpathh = NULL;
}

/* open vpaths */
int vxge_open_vpaths(struct vxgedev *vdev)
{
	enum vxge_hw_status status;
	struct __vxge_hw_device *hldev;

	hldev = (struct __vxge_hw_device  *)pci_get_drvdata(vdev->pdev);

	vdev->vpath.vpathh = &hldev->virtual_path;
	vdev->vpath.fifo.ndev = vdev->ndev;
	vdev->vpath.fifo.pdev = vdev->pdev;
	vdev->vpath.fifo.fifoh = &hldev->virtual_path.fifoh;
	vdev->vpath.ring.ndev = vdev->ndev;
	vdev->vpath.ring.pdev = vdev->pdev;
	vdev->vpath.ring.ringh = &hldev->virtual_path.ringh;

	status = vxge_hw_vpath_open(vdev->devh,	&vdev->vpath);
	if (status == VXGE_HW_OK) {
		vdev->vpath.is_open = 1;
	} else {
		vxge_debug(VXGE_ERR,
			"%s: vpath: %d failed to open "
			"with status: %d\n",
			vdev->ndev->name, vdev->vpath.device_id,
			status);
		vxge_close_vpaths(vdev);
		return status;
	}

	hldev->vpaths_deployed |= vxge_mBIT(vdev->vpath.vpathh->vp_id);

	return VXGE_HW_OK;
}

/** Functions that implement the iPXE driver API **/

/**
 * vxge_xmit
 * @skb : the socket buffer containing the Tx data.
 * @dev : device pointer.
 *
 * This function is the Tx entry point of the driver. Neterion NIC supports
 * certain protocol assist features on Tx side, namely  CSO, S/G, LSO.
 */
static int
vxge_xmit(struct net_device *dev, struct io_buffer *iobuf)
{
	struct vxge_fifo *fifo = NULL;
	struct vxgedev *vdev = NULL;
	struct __vxge_hw_fifo *fifoh;
	struct vxge_hw_fifo_txd *txdp;

	vxge_trace();

	vdev = (struct vxgedev *)netdev_priv(dev);

	if (!is_vxge_card_up(vdev)) {
		vxge_debug(VXGE_ERR,
			"%s: vdev not initialized\n", dev->name);
		return -EIO;
	}

	if (!netdev_link_ok(dev)) {
		vxge_debug(VXGE_ERR,
			"%s: Link down, transmit failed\n", dev->name);
		return -ENETDOWN;
	}

	fifo = &vdev->vpath.fifo;
	fifoh = fifo->fifoh;

	txdp = vxge_hw_fifo_free_txdl_get(fifoh);
	if (!txdp) {
		vxge_debug(VXGE_ERR,
			"%s: Out of tx descriptors\n", dev->name);
		return -ENOBUFS;
	}

	vxge_debug(VXGE_XMIT, "%s: %s:%d fifoh offset= %d\n",
		dev->name, __func__, __LINE__, fifoh->sw_offset);

	vxge_hw_fifo_txdl_buffer_set(fifoh, txdp, iobuf);

	vxge_hw_fifo_txdl_post(fifoh, txdp);

	return 0;
}

/*
 *  vxge_poll
 *  @ndev: net device pointer
 *
 *  This function acks the interrupt. It polls for rx packets
 *  and send to upper layer. It also checks for tx completion
 *  and frees iobs.
 */
static void vxge_poll(struct net_device *ndev)
{
	struct __vxge_hw_device  *hldev;
	struct vxgedev *vdev;

	vxge_debug(VXGE_POLL, "%s:%d \n", __func__, __LINE__);

	vdev = (struct vxgedev *)netdev_priv(ndev);
	hldev = (struct __vxge_hw_device *)pci_get_drvdata(vdev->pdev);

	if (!is_vxge_card_up(vdev))
		return;

	/* process alarm and acknowledge the interrupts */
	vxge_hw_device_begin_irq(hldev);

	vxge_hw_vpath_poll_tx(&hldev->virtual_path.fifoh);

	vxge_hw_vpath_poll_rx(&hldev->virtual_path.ringh);
}

/*
 * vxge_irq - enable or Disable interrupts
 *
 * @netdev   netdevice structure reference
 * @action   requested interrupt action
 */
static void vxge_irq(struct net_device *netdev __unused, int action)
{
	struct __vxge_hw_device  *hldev;
	struct vxgedev *vdev;

	vxge_debug(VXGE_INFO,
		"%s:%d action(%d)\n", __func__, __LINE__, action);

	vdev = (struct vxgedev *)netdev_priv(netdev);
	hldev = (struct __vxge_hw_device *)pci_get_drvdata(vdev->pdev);

	switch (action) {
	case DISABLE:
		vxge_hw_device_mask_all(hldev);
		break;
	default:
		vxge_hw_device_unmask_all(hldev);
		break;
	}
}

/**
 * vxge_open
 * @dev: pointer to the device structure.
 *
 * This function is the open entry point of the driver. It mainly calls a
 * function to allocate Rx buffers and inserts them into the buffer
 * descriptors and then enables the Rx part of the NIC.
 * Return value: '0' on success and an appropriate (-)ve integer as
 * defined in errno.h file on failure.
 */
int
vxge_open(struct net_device *dev)
{
	enum vxge_hw_status status;
	struct vxgedev *vdev;
	struct __vxge_hw_device *hldev;
	int ret = 0;

	vxge_debug(VXGE_INFO, "%s: %s:%d\n",
			VXGE_DRIVER_NAME, __func__, __LINE__);

	vdev = (struct vxgedev *)netdev_priv(dev);
	hldev = (struct __vxge_hw_device *)pci_get_drvdata(vdev->pdev);

	/* make sure you have link off by default every time Nic is
	 * initialized */
	netdev_link_down(dev);

	/* Open VPATHs */
	status = vxge_open_vpaths(vdev);
	if (status != VXGE_HW_OK) {
		vxge_debug(VXGE_ERR, "%s: fatal: Vpath open failed\n",
				VXGE_DRIVER_NAME);
		ret = -EPERM;
		goto out0;
	}

	vdev->mtu = VXGE_HW_DEFAULT_MTU;
	/* set initial mtu before enabling the device */
	status = vxge_hw_vpath_mtu_set(vdev->vpath.vpathh, vdev->mtu);
	if (status != VXGE_HW_OK) {
		vxge_debug(VXGE_ERR,
			"%s: fatal: can not set new MTU\n", dev->name);
		ret = -EPERM;
		goto out2;
	}
	vxge_debug(VXGE_INFO,
		"%s: MTU is %d\n", vdev->ndev->name, vdev->mtu);

	set_bit(__VXGE_STATE_CARD_UP, vdev->state);

	wmb();

	if (vxge_hw_device_link_state_get(vdev->devh) == VXGE_HW_LINK_UP) {
		netdev_link_up(vdev->ndev);
		vxge_debug(VXGE_INFO, "%s: Link Up\n", vdev->ndev->name);
	}

	vxge_hw_device_intr_enable(hldev);

	vxge_hw_vpath_enable(vdev->vpath.vpathh);
	wmb();
	vxge_hw_vpath_rx_doorbell_init(vdev->vpath.vpathh);

	goto out0;

out2:
	vxge_close_vpaths(vdev);
out0:
	vxge_debug(VXGE_INFO, "%s: %s:%d  Exiting...\n",
				dev->name, __func__, __LINE__);
	return ret;
}

/**
 * vxge_close
 * @dev: device pointer.
 *
 * This is the stop entry point of the driver. It needs to undo exactly
 * whatever was done by the open entry point, thus it's usually referred to
 * as the close function.Among other things this function mainly stops the
 * Rx side of the NIC and frees all the Rx buffers in the Rx rings.
 * Return value: '0' on success and an appropriate (-)ve integer as
 * defined in errno.h file on failure.
 */
static void vxge_close(struct net_device *dev)
{
	struct vxgedev *vdev;
	struct __vxge_hw_device *hldev;

	vxge_debug(VXGE_INFO, "%s: %s:%d\n",
		dev->name, __func__, __LINE__);

	vdev = (struct vxgedev *)netdev_priv(dev);
	hldev = (struct __vxge_hw_device *)pci_get_drvdata(vdev->pdev);

	if (!is_vxge_card_up(vdev))
		return;

	clear_bit(__VXGE_STATE_CARD_UP, vdev->state);

	vxge_hw_vpath_set_zero_rx_frm_len(hldev);

	netdev_link_down(vdev->ndev);
	vxge_debug(VXGE_INFO, "%s: Link Down\n", vdev->ndev->name);

	/* Note that at this point xmit() is stopped by upper layer */
	vxge_hw_device_intr_disable(hldev);

	/* Multi function shares INTA, hence we should
	 * leave it in enabled state
	 */
	if (is_mf(hldev->hw_info.function_mode))
		vxge_hw_device_unmask_all(hldev);

	vxge_reset_all_vpaths(vdev);

	vxge_close_vpaths(vdev);

	vxge_debug(VXGE_INFO,
		"%s: %s:%d  Exiting...\n", dev->name, __func__, __LINE__);
}

static struct net_device_operations vxge_operations;

int vxge_device_register(struct __vxge_hw_device *hldev,
				struct vxgedev **vdev_out)
{
	struct net_device *ndev;
	struct vxgedev *vdev;
	int ret = 0;

	*vdev_out = NULL;

	ndev = alloc_etherdev(sizeof(struct vxgedev));
	if (ndev == NULL) {
		vxge_debug(VXGE_ERR, "%s : device allocation failed\n",
				__func__);
		ret = -ENODEV;
		goto _out0;
	}

	vxge_debug(VXGE_INFO, "%s:%d  netdev registering\n",
		__func__, __LINE__);
	vdev = netdev_priv(ndev);
	memset(vdev, 0, sizeof(struct vxgedev));

	vdev->ndev = ndev;
	vdev->devh = hldev;
	vdev->pdev = hldev->pdev;

	ndev->dev = &vdev->pdev->dev;
	/* Associate vxge-specific network operations operations with
	 * generic network device layer */
	netdev_init(ndev, &vxge_operations);

	memcpy(ndev->hw_addr,
		(u8 *)hldev->hw_info.mac_addrs[hldev->first_vp_id], ETH_ALEN);

	if (register_netdev(ndev)) {
		vxge_debug(VXGE_ERR, "%s : device registration failed!\n",
			__func__);
		ret = -ENODEV;
		goto _out2;
	}

	/* Leave link state as off at this point, when the link change
	 * interrupt comes the state will be automatically changed to
	 * the right state.
	 */

	vxge_debug(VXGE_INFO, "%s: Ethernet device registered\n",
		VXGE_DRIVER_NAME);

	*vdev_out = vdev;

	return ret;
_out2:
	netdev_put(ndev);
_out0:
	return ret;
}

/*
 * vxge_device_unregister
 *
 * This function will unregister and free network device
 */
void
vxge_device_unregister(struct __vxge_hw_device *hldev)
{
	struct net_device *ndev;

	ndev = hldev->ndev;

	unregister_netdev(ndev);
	netdev_nullify(ndev);
	netdev_put(ndev);

	vxge_debug(VXGE_INFO, "%s: ethernet device unregistered\n",
				VXGE_DRIVER_NAME);
}

/**
 * vxge_probe
 * @pdev : structure containing the PCI related information of the device.
 * @id: List of PCI devices supported by the driver listed in vxge_id_table.
 * Description:
 * This function is called when a new PCI device gets detected and initializes
 * it.
 * Return value:
 * returns 0 on success and negative on failure.
 *
 */
static int
vxge_probe(struct pci_device *pdev)
{
	struct __vxge_hw_device  *hldev;
	enum vxge_hw_status status;
	int ret = 0;
	u64 vpath_mask = 0;
	struct vxgedev *vdev;
	int i;
	u8 revision, titan1;
	u32 function_mode;
	unsigned long mmio_start, mmio_len;
	void *bar0;
	struct vxge_hw_device_hw_info hw_info;
	struct vxge_hw_device_version *fw_version;

	vxge_debug(VXGE_INFO, "vxge_probe for device " PCI_FMT "\n",
			PCI_ARGS(pdev));

	pci_read_config_byte(pdev, PCI_REVISION_ID, &revision);
	titan1 = is_titan1(pdev->device, revision);

	mmio_start = pci_bar_start(pdev, PCI_BASE_ADDRESS_0);
	mmio_len   = pci_bar_size(pdev, PCI_BASE_ADDRESS_0);
	vxge_debug(VXGE_INFO, "mmio_start: %#08lx, mmio_len: %#08lx\n",
			mmio_start, mmio_len);

	/* sets the bus master */
	adjust_pci_device(pdev);

	bar0 = ioremap(mmio_start, mmio_len);
	if (!bar0) {
		vxge_debug(VXGE_ERR,
			"%s : cannot remap io memory bar0\n", __func__);
		ret = -ENODEV;
		goto _exit0;
	}

	status = vxge_hw_device_hw_info_get(pdev, bar0, &hw_info);
	if (status != VXGE_HW_OK) {
		vxge_debug(VXGE_ERR,
			"%s: Reading of hardware info failed.\n",
			VXGE_DRIVER_NAME);
		ret = -EINVAL;
		goto _exit1;
	}

	if (hw_info.func_id != 0) {
		/* Non zero function, So do not load the driver */
		iounmap(bar0);
		pci_set_drvdata(pdev, NULL);
		return -EINVAL;
	}


	vpath_mask = hw_info.vpath_mask;
	if (vpath_mask == 0) {
		vxge_debug(VXGE_ERR,
			"%s: No vpaths available in device\n",
			VXGE_DRIVER_NAME);
		ret = -EINVAL;
		goto _exit1;
	}
	vxge_debug(VXGE_INFO,
		"%s:%d  Vpath mask = %llx\n", __func__, __LINE__,
		(unsigned long long)vpath_mask);

	fw_version = &hw_info.fw_version;
	/* fail the driver loading if firmware is incompatible */
	if ((fw_version->major != VXGE_CERT_FW_VER_MAJOR) ||
		(fw_version->minor < VXGE_CERT_FW_VER_MINOR)) {
		printf("%s: Adapter's current firmware version: %d.%d.%d\n",
			VXGE_DRIVER_NAME, fw_version->major,
			fw_version->minor, fw_version->build);

		printf("%s: Upgrade firmware to version %d.%d.%d\n",
			VXGE_DRIVER_NAME, VXGE_CERT_FW_VER_MAJOR,
			VXGE_CERT_FW_VER_MINOR,	VXGE_CERT_FW_VER_BUILD);

		ret = -EACCES;
		goto _exit1;
	}

	status = vxge_hw_device_initialize(&hldev, bar0, pdev, titan1);
	if (status != VXGE_HW_OK) {
		vxge_debug(VXGE_ERR,
			"Failed to initialize device (%d)\n", status);
			ret = -EINVAL;
			goto _exit1;
	}
	memcpy(&hldev->hw_info, &hw_info,
		sizeof(struct vxge_hw_device_hw_info));

	/* find the vpath id of the first available one */
	for (i = 0; i < VXGE_HW_MAX_VIRTUAL_PATHS; i++)
		if (vpath_mask & vxge_mBIT(i)) {
			hldev->first_vp_id = i;
			break;
		}
	/* if FCS stripping is not disabled in MAC fail driver load */
	if (vxge_hw_vpath_strip_fcs_check(hldev, vpath_mask) != VXGE_HW_OK) {
		vxge_debug(VXGE_ERR,
			"%s: FCS stripping is not disabled in MAC"
			" failing driver load\n", VXGE_DRIVER_NAME);
		ret = -EINVAL;
		goto _exit2;
	}

	/* Read function mode */
	status = vxge_hw_get_func_mode(hldev, &function_mode);
	if (status != VXGE_HW_OK)
		goto _exit2;

	hldev->hw_info.function_mode = function_mode;

	/* set private device info */
	pci_set_drvdata(pdev, hldev);

	if (vxge_device_register(hldev,	&vdev)) {
		ret = -EINVAL;
		goto _exit2;
	}

	/* set private HW device info */
	hldev->ndev = vdev->ndev;
	hldev->vdev = vdev;
	hldev->pdev = pdev;
	vdev->mtu = VXGE_HW_DEFAULT_MTU;
	vdev->bar0 = bar0;
	vdev->titan1 = titan1;
	/* Virtual Path count */
	vdev->vpath.device_id = hldev->first_vp_id;
	vdev->vpath.vdev = vdev;
	memcpy((u8 *)vdev->vpath.macaddr,
			(u8 *)hldev->hw_info.mac_addrs[hldev->first_vp_id],
			ETH_ALEN);

	hldev->hw_info.serial_number[VXGE_HW_INFO_LEN - 1] = '\0';
	hldev->hw_info.product_desc[VXGE_HW_INFO_LEN - 1] = '\0';
	hldev->hw_info.part_number[VXGE_HW_INFO_LEN - 1] = '\0';

	vxge_debug(VXGE_INFO, "%s: Neterion %s Server Adapter\n",
		VXGE_DRIVER_NAME, hldev->hw_info.product_desc);
	vxge_debug(VXGE_INFO, "%s: SERIAL NUMBER: %s\n",
		VXGE_DRIVER_NAME, hldev->hw_info.serial_number);
	vxge_debug(VXGE_INFO, "%s: PART NUMBER: %s\n",
		VXGE_DRIVER_NAME, hldev->hw_info.part_number);
	vxge_debug(VXGE_INFO, "%s: MAC ADDR: %s\n",
		VXGE_DRIVER_NAME, eth_ntoa(vdev->vpath.macaddr));
	vxge_debug(VXGE_INFO,
		"%s: Firmware version : %s Date : %s\n", VXGE_DRIVER_NAME,
		hldev->hw_info.fw_version.version,
		hldev->hw_info.fw_date.date);
	vxge_debug(VXGE_INFO, "%s: %s Enabled\n",
			VXGE_DRIVER_NAME, vxge_func_mode_names[function_mode]);

	vxge_debug(VXGE_INFO, "%s: %s:%d  Probe Exiting...\n",
		VXGE_DRIVER_NAME, __func__, __LINE__);

	return 0;

_exit2:
	vxge_hw_device_terminate(hldev);
_exit1:
	iounmap(bar0);
_exit0:
	pci_set_drvdata(pdev, NULL);
	printf("%s: WARNING!! Driver loading failed!!\n",
		VXGE_DRIVER_NAME);

	return ret;
}

/**
 * vxge_remove - Free the PCI device
 * @pdev: structure containing the PCI related information of the device.
 * Description: This function is called by the Pci subsystem to release a
 * PCI device and free up all resource held up by the device.
 */
static void
vxge_remove(struct pci_device *pdev)
{
	struct __vxge_hw_device  *hldev;
	struct vxgedev *vdev = NULL;
	struct net_device *ndev;

	vxge_debug(VXGE_INFO,
		"%s:%d\n", __func__, __LINE__);
	hldev = (struct __vxge_hw_device  *) pci_get_drvdata(pdev);
	if (hldev == NULL)
		return;

	ndev = hldev->ndev;
	vdev = netdev_priv(ndev);

	iounmap(vdev->bar0);

	vxge_device_unregister(hldev);

	vxge_debug(VXGE_INFO,
		"%s:%d  Device unregistered\n", __func__, __LINE__);

	vxge_hw_device_terminate(hldev);
	pci_set_drvdata(pdev, NULL);
}

/* vxge net device operations */
static struct net_device_operations vxge_operations = {
	.open           = vxge_open,
	.close          = vxge_close,
	.transmit       = vxge_xmit,
	.poll           = vxge_poll,
	.irq            = vxge_irq,
};

static struct pci_device_id vxge_main_nics[] = {
	/* If you change this, also adjust vxge_nics[] in vxge.c */
	PCI_ID(0x17d5, 0x5833, "vxge-x3100", "Neterion X3100 Series", 0),
};

struct pci_driver vxge_driver __pci_driver = {
	.ids = vxge_main_nics,
	.id_count = (sizeof(vxge_main_nics) / sizeof(vxge_main_nics[0])),
	.probe = vxge_probe,
	.remove = vxge_remove,
};
