/*
 * 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 "bnxgld.h"
#include "bnxhwi.h"
#include "bnxint.h"
#include "bnxtmr.h"
#include "bnxcfg.h"

#define	BNX_PRODUCT_BANNER "QLogic 570x/571x Gigabit Ethernet Driver "\
    BRCMVERSION

#define	BNX_PRODUCT_INFO "QLogic 570x/571x GbE "\
    BRCMVERSION

ddi_device_acc_attr_t bnxAccessAttribBAR = {
	DDI_DEVICE_ATTR_V0,	/* devacc_attr_version */
	DDI_STRUCTURE_LE_ACC,	/* devacc_attr_endian_flags */
	DDI_STRICTORDER_ACC,	/* devacc_attr_dataorder */
	DDI_DEFAULT_ACC		/* devacc_attr_access */
};

ddi_device_acc_attr_t bnxAccessAttribBUF = {
	DDI_DEVICE_ATTR_V0,	/* devacc_attr_version */
	DDI_NEVERSWAP_ACC,	/* devacc_attr_endian_flags */
	DDI_STRICTORDER_ACC,	/* devacc_attr_dataorder */
	DDI_DEFAULT_ACC		/* devacc_attr_access */
};


/*
 * Name:    bnx_free_system_resources
 *
 * Input:   ptr to device structure
 *
 * Return:  void
 *
 * Description:
 *          This function is called from detach() entry point to free most
 *          resources held by this device instance.
 */
static int
bnx_free_system_resources(um_device_t * const umdevice)
{
	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_MINOR_NODE) {
		umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_MINOR_NODE;
#ifdef _USE_FRIENDLY_NAME
		ddi_remove_minor_node(umdevice->os_param.dip,
		    (char *)ddi_driver_name(umdevice->os_param.dip));
#else
		ddi_remove_minor_node(umdevice->os_param.dip,
		    ddi_get_name(umdevice->os_param.dip));
#endif
	}

	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_TIMER) {
		umdevice->os_param.active_resc_flag &=
		    ~DRV_RESOURCE_TIMER;
		bnx_timer_fini(umdevice);
	}

	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_GLD_REGISTER) {
		if (bnx_gld_fini(umdevice)) {
			/*
			 * FIXME -- If bnx_gld_fini() fails, we need to
			 * reactivate resources.
			 */
			return (-1);
		}
		umdevice->os_param.active_resc_flag &=
		    ~DRV_RESOURCE_GLD_REGISTER;
	}

	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_KSTAT) {
		umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_KSTAT;
		bnx_kstat_fini(umdevice);
	}

	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_HDWR_REGISTER) {
		umdevice->os_param.active_resc_flag &=
		    ~DRV_RESOURCE_HDWR_REGISTER;
		bnx_hdwr_fini(umdevice);
	}

	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_MUTEX) {
		umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_MUTEX;
		mutex_destroy(&umdevice->os_param.ind_mutex);
		mutex_destroy(&umdevice->os_param.phy_mutex);
		mutex_destroy(&umdevice->os_param.rcv_mutex);
	}

	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_INTR_1) {
		umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_INTR_1;
		bnxIntrFini(umdevice);
	}

	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_MAP_REGS) {
		umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_MAP_REGS;
		ddi_regs_map_free(&umdevice->os_param.reg_acc_handle);
		umdevice->lm_dev.vars.dmaRegAccHandle = NULL;
		umdevice->os_param.reg_acc_handle = NULL;
	}

	if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_PCICFG_MAPPED) {
		umdevice->os_param.active_resc_flag &=
		    ~DRV_RESOURCE_PCICFG_MAPPED;
		pci_config_teardown(&umdevice->os_param.pci_cfg_handle);
	}

	return (0);
}



/*
 * Name:    bnx_attach_attach
 *
 * Input:   ptr to dev_info_t
 *
 * Return:  DDI_SUCCESS or DDI_FAILURE.
 *
 * Description: This is the main code involving all important driver data struct
 *		and device initialization stuff. This function allocates driver
 *		soft state for this instance of the driver,  sets access up
 *		attributes for the device, maps BAR register space, initializes
 *		the hardware, determines interrupt pin, registers interrupt
 *		service routine with the OS and initializes receive/transmit
 *		mutex. After successful completion of above mentioned tasks,
 *		the driver registers with the GLD and creates minor node in
 *		the file system tree for this device.
 */
static int
bnx_attach_attach(um_device_t *umdevice)
{
	int rc;
	int instance;
	unsigned int val;
	int chip_id;
	int device_id;
	int subdevice_id;
	off_t regSize;

	dev_info_t *dip;

	dip = umdevice->os_param.dip;

	umdevice->os_param.active_resc_flag = 0;

	rc = pci_config_setup(umdevice->os_param.dip,
	    &umdevice->os_param.pci_cfg_handle);
	if (rc != DDI_SUCCESS) {
		cmn_err(CE_WARN,
		    "%s: Failed to setup PCI configuration space accesses.\n",
		    umdevice->dev_name);
		goto error;
	}

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_PCICFG_MAPPED;

	rc = ddi_dev_regsize(dip, 1, &regSize);
	if (rc != DDI_SUCCESS) {
		cmn_err(CE_WARN, "%s: failed to determine register set size.",
		    umdevice->dev_name);
	}

	/*
	 * Setup device memory mapping so that LM driver can start accessing it.
	 */
	rc = ddi_regs_map_setup(dip,
	    1, /* BAR */
	    &umdevice->os_param.regs_addr,
	    0, /* OFFSET */
	    regSize,
	    &bnxAccessAttribBAR,
	    &umdevice->os_param.reg_acc_handle);
	if (rc != DDI_SUCCESS) {
		cmn_err(CE_WARN,
		    "%s: Failed to memory map device.\n",
		    umdevice->dev_name);
		goto error;
	}

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_MAP_REGS;

	bnx_cfg_msix(umdevice);

	if (bnxIntrInit(umdevice) != 0) {
		goto error;
	}

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_INTR_1;

	mutex_init(&umdevice->os_param.rcv_mutex, NULL,
	    MUTEX_DRIVER, DDI_INTR_PRI(umdevice->intrPriority));
	mutex_init(&umdevice->os_param.phy_mutex, NULL,
	    MUTEX_DRIVER, DDI_INTR_PRI(umdevice->intrPriority));
	mutex_init(&umdevice->os_param.ind_mutex, NULL,
	    MUTEX_DRIVER, DDI_INTR_PRI(umdevice->intrPriority));

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_MUTEX;

	/*
	 * Call lower module's initialization routines to initialize
	 * hardware and related components within BNX.
	 */
	if (bnx_hdwr_init(umdevice)) {
		goto error;
	}

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_HDWR_REGISTER;

	if (!bnx_kstat_init(umdevice)) {
		goto error;
	}

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_KSTAT;

	if (bnx_gld_init(umdevice)) {
		goto error;
	}

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_GLD_REGISTER;

	bnx_timer_init(umdevice);

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_TIMER;

	instance = ddi_get_instance(umdevice->os_param.dip);

	/* Create a minor node entry in /devices . */
#ifdef _USE_FRIENDLY_NAME
	rc = ddi_create_minor_node(dip, (char *)ddi_driver_name(dip),
	    S_IFCHR, instance, DDI_PSEUDO, 0);
#else
	rc = ddi_create_minor_node(dip, ddi_get_name(dip),
	    S_IFCHR, instance, DDI_PSEUDO, 0);
#endif
	if (rc == DDI_FAILURE) {
		cmn_err(CE_WARN, "%s: Failed to create device minor node.\n",
		    umdevice->dev_name);
		goto error;
	}

	umdevice->os_param.active_resc_flag |= DRV_RESOURCE_MINOR_NODE;

	ddi_report_dev(dip);

	device_id = pci_config_get16(umdevice->os_param.pci_cfg_handle,
	    0x2);
	subdevice_id = pci_config_get16(umdevice->os_param.pci_cfg_handle,
	    0x2e);

	/*  Dip into PCI config space to determine if we have 5716's */
	if ((device_id == 0x163b) && (subdevice_id == 0x163b)) {
		chip_id = 0x5716;
	} else {
		chip_id = CHIP_NUM(&umdevice->lm_dev) >> 16;
	}

	(void) snprintf(umdevice->version, sizeof (umdevice->version), "%s",
	    BRCMVERSION);

	/* Get firmware version. */
	REG_RD_IND(&umdevice->lm_dev,
	    umdevice->lm_dev.hw_info.shmem_base +
	    OFFSETOF(shmem_region_t, dev_info.bc_rev), &val);
	umdevice->dev_var.fw_ver = (val & 0xFFFF0000) | ((val & 0xFF00) >> 8);

	(void) snprintf(umdevice->versionFW, sizeof (umdevice->versionFW),
	    "0x%x", umdevice->dev_var.fw_ver);

	(void) snprintf(umdevice->chipName, sizeof (umdevice->chipName),
	    "BCM%x", chip_id);

	(void) snprintf(umdevice->intrAlloc, sizeof (umdevice->intrAlloc),
	    "1 %s", (umdevice->intrType == DDI_INTR_TYPE_MSIX) ? "MSIX" :
	    (umdevice->intrType == DDI_INTR_TYPE_MSI)  ? "MSI"  :
	    "Fixed");

	cmn_err(CE_NOTE,
	    "!%s: (%s) BCM%x device with F/W Ver%x is initialized (%s)",
	    umdevice->dev_name, umdevice->version,
	    chip_id, umdevice->dev_var.fw_ver,
	    umdevice->intrAlloc);

	return (0);

error:
	(void) bnx_free_system_resources(umdevice);

	return (-1);
}

/*
 * Name:    bnx_attach
 *
 * Input:   ptr to dev_info_t, command code for the task to be executed
 *
 * Return:  DDI_SUCCESS or DDI_FAILURE.
 *
 * Description:	OS determined module attach entry point.
 */
static int
bnx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	um_device_t *umdevice;
	int ret_val = DDI_SUCCESS;

	switch (cmd) {
		case DDI_ATTACH:
			umdevice = kmem_zalloc(sizeof (um_device_t),
			    KM_NOSLEEP);
			if (umdevice == NULL) {
				cmn_err(CE_WARN, "%s: Failed to allocate "
				    "device memory.\n", __func__);
				ret_val = DDI_FAILURE;
				break;
			}

			/* Save dev_info_t info in the driver struture. */
			umdevice->os_param.dip = dip;

			/*
			 * Obtain a human-readable name to prepend all our
			 * messages with.
			 */
			umdevice->instance = ddi_get_instance(dip);
			(void) snprintf(umdevice->dev_name,
			    sizeof (umdevice->dev_name), "%s%d", "bnx",
			    umdevice->instance);

			/*
			 * Set driver private pointer to per device structure
			 * ptr.
			 */
			ddi_set_driver_private(dip, (caddr_t)umdevice);

			umdevice->magic = BNX_MAGIC;

			if (bnx_attach_attach(umdevice)) {
				ddi_set_driver_private(dip, (caddr_t)NULL);
				kmem_free(umdevice, sizeof (um_device_t));
				ret_val = DDI_FAILURE;
			}
			break;

		case DDI_RESUME:
			/* Retrieve our device structure. */
			umdevice = ddi_get_driver_private(dip);
			if (umdevice == NULL) {
				ret_val = DDI_FAILURE;
				break;
			}
			break;

		default:
			ret_val = DDI_FAILURE;
			break;
	}

	return (ret_val);
}

/*
 * Name:    bnx_detach
 *
 * Input:   ptr to dev_info_t, command code for the task to be executed
 *
 * Return:  DDI_SUCCESS or DDI_FAILURE.
 *
 * Description:	OS determined module detach entry point.
 */
static int
bnx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	um_device_t *umdevice;
	int ret_val = DDI_SUCCESS;

	switch (cmd) {
		case DDI_DETACH:
			umdevice = ddi_get_driver_private(dip);
			if (umdevice == NULL) {
				/* Must have failed attach. */
				ret_val = DDI_SUCCESS;
				break;
			}

			/* Sanity check. */
			if (umdevice == NULL) {
				cmn_err(CE_WARN,
				    "%s: Sanity check failed(1).", __func__);
				ret_val = DDI_SUCCESS;
				break;
			}

			/* Sanity check. */
			if (umdevice->os_param.dip != dip) {
				cmn_err(CE_WARN,
				    "%s: Sanity check failed(2).", __func__);
				ret_val = DDI_SUCCESS;
				break;
			}

			/* Another sanity check. */
			if (umdevice->intr_enabled != B_FALSE) {
				cmn_err(CE_WARN, "%s: Detaching a device "
				    "that is currently running!!!\n",
				    umdevice->dev_name);
				ret_val = DDI_FAILURE;
				break;
			}

			if (bnx_free_system_resources(umdevice)) {
				ret_val = DDI_FAILURE;
				break;
			}

			ddi_set_driver_private(dip, (caddr_t)NULL);
			kmem_free(umdevice, sizeof (um_device_t));
			break;

		case DDI_SUSPEND:
			/* Retrieve our device structure. */
			umdevice = ddi_get_driver_private(dip);
			if (umdevice == NULL) {
				ret_val = DDI_FAILURE;
				break;
			}
			break;

		default:
			ret_val = DDI_FAILURE;
			break;
	}

	return (ret_val);
}

/*
 * Name:    bnx_quiesce
 *
 * Input:   ptr to dev_info_t
 *
 * Return:  DDI_SUCCESS or DDI_FAILURE.
 *
 * Description: quiesce(9E) entry point.
 *              This function will make sure no more interrupts and DMA of
 *              the hardware. It is called when the system is single-threaded
 *              at high PIL with preemption disabled. Thus this function should
 *              not be blocked.
 */
static int
bnx_quiesce(dev_info_t *dip)
{
	um_device_t *umdevice;

	umdevice = ddi_get_driver_private(dip);

	/* Sanity check. */
	if (umdevice == NULL || umdevice->os_param.dip != dip) {
		cmn_err(CE_WARN, "%s: Sanity check failed.", __func__);
		return (DDI_FAILURE);
	}

	/* Stop the device from generating any interrupts. */
	lm_disable_int(&(umdevice->lm_dev));

	/* Set RX mask to stop receiving any further packets */
	(void) lm_set_rx_mask(&(umdevice->lm_dev), RX_FILTER_USER_IDX0,
	    LM_RX_MASK_ACCEPT_NONE);

	return (DDI_SUCCESS);
}

DDI_DEFINE_STREAM_OPS(bnx_dev_ops, nulldev, nulldev, bnx_attach, bnx_detach, \
    nodev, NULL, (D_MP | D_64BIT), NULL, bnx_quiesce);

static struct modldrv bnx_modldrv = {
	&mod_driverops,		/* drv_modops */
	BNX_PRODUCT_INFO,	/* drv_linkinfo */
	&bnx_dev_ops		/* drv_dev_ops */
};

static struct modlinkage bnx_modlinkage = {
	MODREV_1,	/* ml_rev */
	&bnx_modldrv,	/* ml_linkage */
	NULL		/* NULL termination */
};

/*
 * Name:        _init
 *
 * Input:       None
 *
 * Return:      SUCCESS or FAILURE.
 *
 * Description: OS determined driver module load entry point.
 */
int
_init(void)
{
	int rc;

	mac_init_ops(&bnx_dev_ops, "bnx");

	/* Install module information with O/S */
	rc = mod_install(&bnx_modlinkage);
	if (rc != 0) {
		cmn_err(CE_WARN, "%s:_init - mod_install returned 0x%x", "bnx",
		    rc);
		return (rc);
	}

	cmn_err(CE_NOTE, "!%s", BNX_PRODUCT_BANNER);

	return (rc);
}



/*
 * Name:        _fini
 *
 * Input:       None
 *
 * Return:      SUCCESS or FAILURE.
 *
 * Description: OS determined driver module unload entry point.
 */
int
_fini(void)
{
	int rc;

	rc = mod_remove(&bnx_modlinkage);

	if (rc == 0) {
		mac_fini_ops(&bnx_dev_ops);
	}

	return (rc);
}

/*
 * Name:        _info
 *
 * Input:       None
 *
 * Return:      SUCCESS or FAILURE.
 *
 * Description: OS determined module info entry point.
 */
int
_info(struct modinfo *modinfop)
{
	int rc;

	rc = mod_info(&bnx_modlinkage, modinfop);

	return (rc);
}