Skip to content
Snippets Groups Projects
qlcnic_83xx_hw.c 85.8 KiB
Newer Older
/*
 * QLogic qlcnic NIC Driver
 * Copyright (c) 2009-2013 QLogic Corporation
 *
 * See LICENSE.qlcnic for copyright and licensing details.
 */

#include "qlcnic.h"
Rajesh Borundia's avatar
Rajesh Borundia committed
#include "qlcnic_sriov.h"
#include <linux/if_vlan.h>
#include <linux/ipv6.h>
#include <linux/ethtool.h>
#include <linux/interrupt.h>

#define QLCNIC_MAX_TX_QUEUES		1
#define RSS_HASHTYPE_IP_TCP		0x3
#define QLC_83XX_FW_MBX_CMD		0

static const struct qlcnic_mailbox_metadata qlcnic_83xx_mbx_tbl[] = {
	{QLCNIC_CMD_CONFIGURE_IP_ADDR, 6, 1},
	{QLCNIC_CMD_CONFIG_INTRPT, 18, 34},
	{QLCNIC_CMD_CREATE_RX_CTX, 136, 27},
	{QLCNIC_CMD_DESTROY_RX_CTX, 2, 1},
	{QLCNIC_CMD_CREATE_TX_CTX, 54, 18},
	{QLCNIC_CMD_DESTROY_TX_CTX, 2, 1},
	{QLCNIC_CMD_CONFIGURE_MAC_LEARNING, 2, 1},
	{QLCNIC_CMD_INTRPT_TEST, 22, 12},
	{QLCNIC_CMD_SET_MTU, 3, 1},
	{QLCNIC_CMD_READ_PHY, 4, 2},
	{QLCNIC_CMD_WRITE_PHY, 5, 1},
	{QLCNIC_CMD_READ_HW_REG, 4, 1},
	{QLCNIC_CMD_GET_FLOW_CTL, 4, 2},
	{QLCNIC_CMD_SET_FLOW_CTL, 4, 1},
	{QLCNIC_CMD_READ_MAX_MTU, 4, 2},
	{QLCNIC_CMD_READ_MAX_LRO, 4, 2},
	{QLCNIC_CMD_MAC_ADDRESS, 4, 3},
	{QLCNIC_CMD_GET_PCI_INFO, 1, 66},
	{QLCNIC_CMD_GET_NIC_INFO, 2, 19},
	{QLCNIC_CMD_SET_NIC_INFO, 32, 1},
	{QLCNIC_CMD_GET_ESWITCH_CAPABILITY, 4, 3},
	{QLCNIC_CMD_TOGGLE_ESWITCH, 4, 1},
	{QLCNIC_CMD_GET_ESWITCH_STATUS, 4, 3},
	{QLCNIC_CMD_SET_PORTMIRRORING, 4, 1},
	{QLCNIC_CMD_CONFIGURE_ESWITCH, 4, 1},
	{QLCNIC_CMD_GET_ESWITCH_PORT_CONFIG, 4, 3},
	{QLCNIC_CMD_GET_ESWITCH_STATS, 5, 1},
	{QLCNIC_CMD_CONFIG_PORT, 4, 1},
	{QLCNIC_CMD_TEMP_SIZE, 1, 4},
	{QLCNIC_CMD_GET_TEMP_HDR, 5, 5},
	{QLCNIC_CMD_GET_LINK_EVENT, 2, 1},
	{QLCNIC_CMD_CONFIG_MAC_VLAN, 4, 3},
	{QLCNIC_CMD_CONFIG_INTR_COAL, 6, 1},
	{QLCNIC_CMD_CONFIGURE_RSS, 14, 1},
	{QLCNIC_CMD_CONFIGURE_LED, 2, 1},
	{QLCNIC_CMD_CONFIGURE_MAC_RX_MODE, 2, 1},
	{QLCNIC_CMD_CONFIGURE_HW_LRO, 2, 1},
	{QLCNIC_CMD_GET_STATISTICS, 2, 80},
	{QLCNIC_CMD_SET_PORT_CONFIG, 2, 1},
	{QLCNIC_CMD_GET_PORT_CONFIG, 2, 2},
	{QLCNIC_CMD_GET_LINK_STATUS, 2, 4},
	{QLCNIC_CMD_IDC_ACK, 5, 1},
	{QLCNIC_CMD_INIT_NIC_FUNC, 2, 1},
	{QLCNIC_CMD_STOP_NIC_FUNC, 2, 1},
	{QLCNIC_CMD_SET_LED_CONFIG, 5, 1},
	{QLCNIC_CMD_GET_LED_CONFIG, 1, 5},
	{QLCNIC_CMD_ADD_RCV_RINGS, 130, 26},
	{QLCNIC_CMD_CONFIG_VPORT, 4, 4},
	{QLCNIC_CMD_BC_EVENT_SETUP, 2, 1},
Rajesh Borundia's avatar
Rajesh Borundia committed
const u32 qlcnic_83xx_ext_reg_tbl[] = {
	0x38CC,		/* Global Reset */
	0x38F0,		/* Wildcard */
	0x38FC,		/* Informant */
	0x3038,		/* Host MBX ctrl */
	0x303C,		/* FW MBX ctrl */
	0x355C,		/* BOOT LOADER ADDRESS REG */
	0x3560,		/* BOOT LOADER SIZE REG */
	0x3564,		/* FW IMAGE ADDR REG */
	0x1000,		/* MBX intr enable */
	0x1200,		/* Default Intr mask */
	0x1204,		/* Default Interrupt ID */
	0x3780,		/* QLC_83XX_IDC_MAJ_VERSION */
	0x3784,		/* QLC_83XX_IDC_DEV_STATE */
	0x3788,		/* QLC_83XX_IDC_DRV_PRESENCE */
	0x378C,		/* QLC_83XX_IDC_DRV_ACK */
	0x3790,		/* QLC_83XX_IDC_CTRL */
	0x3794,		/* QLC_83XX_IDC_DRV_AUDIT */
	0x3798,		/* QLC_83XX_IDC_MIN_VERSION */
	0x379C,		/* QLC_83XX_RECOVER_DRV_LOCK */
	0x37A0,		/* QLC_83XX_IDC_PF_0 */
	0x37A4,		/* QLC_83XX_IDC_PF_1 */
	0x37A8,		/* QLC_83XX_IDC_PF_2 */
	0x37AC,		/* QLC_83XX_IDC_PF_3 */
	0x37B0,		/* QLC_83XX_IDC_PF_4 */
	0x37B4,		/* QLC_83XX_IDC_PF_5 */
	0x37B8,		/* QLC_83XX_IDC_PF_6 */
	0x37BC,		/* QLC_83XX_IDC_PF_7 */
	0x37C0,		/* QLC_83XX_IDC_PF_8 */
	0x37C4,		/* QLC_83XX_IDC_PF_9 */
	0x37C8,		/* QLC_83XX_IDC_PF_10 */
	0x37CC,		/* QLC_83XX_IDC_PF_11 */
	0x37D0,		/* QLC_83XX_IDC_PF_12 */
	0x37D4,		/* QLC_83XX_IDC_PF_13 */
	0x37D8,		/* QLC_83XX_IDC_PF_14 */
	0x37DC,		/* QLC_83XX_IDC_PF_15 */
	0x37E0,		/* QLC_83XX_IDC_DEV_PARTITION_INFO_1 */
	0x37E4,		/* QLC_83XX_IDC_DEV_PARTITION_INFO_2 */
	0x37F0,		/* QLC_83XX_DRV_OP_MODE */
	0x37F4,		/* QLC_83XX_VNIC_STATE */
	0x3868,		/* QLC_83XX_DRV_LOCK */
	0x386C,		/* QLC_83XX_DRV_UNLOCK */
	0x3504,		/* QLC_83XX_DRV_LOCK_ID */
	0x34A4,		/* QLC_83XX_ASIC_TEMP */
};

Rajesh Borundia's avatar
Rajesh Borundia committed
const u32 qlcnic_83xx_reg_tbl[] = {
	0x34A8,		/* PEG_HALT_STAT1 */
	0x34AC,		/* PEG_HALT_STAT2 */
	0x34B0,		/* FW_HEARTBEAT */
	0x3500,		/* FLASH LOCK_ID */
	0x3528,		/* FW_CAPABILITIES */
	0x3538,		/* Driver active, DRV_REG0 */
	0x3540,		/* Device state, DRV_REG1 */
	0x3544,		/* Driver state, DRV_REG2 */
	0x3548,		/* Driver scratch, DRV_REG3 */
	0x354C,		/* Device partiton info, DRV_REG4 */
	0x3524,		/* Driver IDC ver, DRV_REG5 */
	0x3550,		/* FW_VER_MAJOR */
	0x3554,		/* FW_VER_MINOR */
	0x3558,		/* FW_VER_SUB */
	0x359C,		/* NPAR STATE */
	0x35FC,		/* FW_IMG_VALID */
	0x3650,		/* CMD_PEG_STATE */
	0x373C,		/* RCV_PEG_STATE */
	0x37B4,		/* ASIC TEMP */
	0x356C,		/* FW API */
	0x3570,		/* DRV OP MODE */
	0x3850,		/* FLASH LOCK */
	0x3854,		/* FLASH UNLOCK */
};

static struct qlcnic_hardware_ops qlcnic_83xx_hw_ops = {
	.read_crb			= qlcnic_83xx_read_crb,
	.write_crb			= qlcnic_83xx_write_crb,
	.read_reg			= qlcnic_83xx_rd_reg_indirect,
	.write_reg			= qlcnic_83xx_wrt_reg_indirect,
	.get_mac_address		= qlcnic_83xx_get_mac_address,
	.setup_intr			= qlcnic_83xx_setup_intr,
	.alloc_mbx_args			= qlcnic_83xx_alloc_mbx_args,
	.mbx_cmd			= qlcnic_83xx_mbx_op,
	.get_func_no			= qlcnic_83xx_get_func_no,
	.api_lock			= qlcnic_83xx_cam_lock,
	.api_unlock			= qlcnic_83xx_cam_unlock,
Sony Chacko's avatar
Sony Chacko committed
	.add_sysfs			= qlcnic_83xx_add_sysfs,
	.remove_sysfs			= qlcnic_83xx_remove_sysfs,
	.process_lb_rcv_ring_diag	= qlcnic_83xx_process_rcv_ring_diag,
	.create_rx_ctx			= qlcnic_83xx_create_rx_ctx,
	.create_tx_ctx			= qlcnic_83xx_create_tx_ctx,
	.del_rx_ctx			= qlcnic_83xx_del_rx_ctx,
	.del_tx_ctx			= qlcnic_83xx_del_tx_ctx,
	.setup_link_event		= qlcnic_83xx_setup_link_event,
	.get_nic_info			= qlcnic_83xx_get_nic_info,
	.get_pci_info			= qlcnic_83xx_get_pci_info,
	.set_nic_info			= qlcnic_83xx_set_nic_info,
	.change_macvlan			= qlcnic_83xx_sre_macaddr_change,
	.napi_enable			= qlcnic_83xx_napi_enable,
	.napi_disable			= qlcnic_83xx_napi_disable,
	.config_intr_coal		= qlcnic_83xx_config_intr_coal,
	.config_rss			= qlcnic_83xx_config_rss,
	.config_hw_lro			= qlcnic_83xx_config_hw_lro,
	.config_promisc_mode		= qlcnic_83xx_nic_set_promisc,
	.change_l2_filter		= qlcnic_83xx_change_l2_filter,
	.get_board_info			= qlcnic_83xx_get_port_info,
	.free_mac_list			= qlcnic_82xx_free_mac_list,
};

static struct qlcnic_nic_template qlcnic_83xx_ops = {
	.config_bridged_mode	= qlcnic_config_bridged_mode,
	.config_led		= qlcnic_config_led,
	.request_reset          = qlcnic_83xx_idc_request_reset,
	.cancel_idc_work        = qlcnic_83xx_idc_exit,
	.napi_add		= qlcnic_83xx_napi_add,
	.napi_del		= qlcnic_83xx_napi_del,
	.config_ipaddr		= qlcnic_83xx_config_ipaddr,
	.clear_legacy_intr	= qlcnic_83xx_clear_legacy_intr,
};

void qlcnic_83xx_register_map(struct qlcnic_hardware_context *ahw)
{
	ahw->hw_ops		= &qlcnic_83xx_hw_ops;
	ahw->reg_tbl		= (u32 *)qlcnic_83xx_reg_tbl;
	ahw->ext_reg_tbl	= (u32 *)qlcnic_83xx_ext_reg_tbl;
}

int qlcnic_83xx_get_fw_version(struct qlcnic_adapter *adapter)
{
	u32 fw_major, fw_minor, fw_build;
	struct pci_dev *pdev = adapter->pdev;

	fw_major = QLC_SHARED_REG_RD32(adapter, QLCNIC_FW_VERSION_MAJOR);
	fw_minor = QLC_SHARED_REG_RD32(adapter, QLCNIC_FW_VERSION_MINOR);
	fw_build = QLC_SHARED_REG_RD32(adapter, QLCNIC_FW_VERSION_SUB);
	adapter->fw_version = QLCNIC_VERSION_CODE(fw_major, fw_minor, fw_build);

	dev_info(&pdev->dev, "Driver v%s, firmware version %d.%d.%d\n",
		 QLCNIC_LINUX_VERSIONID, fw_major, fw_minor, fw_build);

	return adapter->fw_version;
}

static int __qlcnic_set_win_base(struct qlcnic_adapter *adapter, u32 addr)
{
	void __iomem *base;
	u32 val;

	base = adapter->ahw->pci_base0 +
	       QLC_83XX_CRB_WIN_FUNC(adapter->ahw->pci_func);
	writel(addr, base);
	val = readl(base);
	if (val != addr)
		return -EIO;

	return 0;
}

int qlcnic_83xx_rd_reg_indirect(struct qlcnic_adapter *adapter, ulong addr)
{
	int ret;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	ret = __qlcnic_set_win_base(adapter, (u32) addr);
	if (!ret) {
		return QLCRDX(ahw, QLCNIC_WILDCARD);
	} else {
		dev_err(&adapter->pdev->dev,
			"%s failed, addr = 0x%x\n", __func__, (int)addr);
		return -EIO;
	}
}

int qlcnic_83xx_wrt_reg_indirect(struct qlcnic_adapter *adapter, ulong addr,
				 u32 data)
{
	int err;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	err = __qlcnic_set_win_base(adapter, (u32) addr);
	if (!err) {
		QLCWRX(ahw, QLCNIC_WILDCARD, data);
		return 0;
	} else {
		dev_err(&adapter->pdev->dev,
			"%s failed, addr = 0x%x data = 0x%x\n",
			__func__, (int)addr, data);
		return err;
	}
}

int qlcnic_83xx_setup_intr(struct qlcnic_adapter *adapter, u8 num_intr)
{
	int err, i, num_msix;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	if (!num_intr)
		num_intr = QLCNIC_DEF_NUM_STS_DESC_RINGS;
	num_msix = rounddown_pow_of_two(min_t(int, num_online_cpus(),
					      num_intr));
	/* account for AEN interrupt MSI-X based interrupts */
	num_msix += 1;

	if (!(adapter->flags & QLCNIC_TX_INTR_SHARED))
		num_msix += adapter->max_drv_tx_rings;

	err = qlcnic_enable_msix(adapter, num_msix);
	if (err == -ENOMEM)
		return err;
	if (adapter->flags & QLCNIC_MSIX_ENABLED)
		num_msix = adapter->ahw->num_msix;
Rajesh Borundia's avatar
Rajesh Borundia committed
	else {
		if (qlcnic_sriov_vf_check(adapter))
			return -EINVAL;
	/* setup interrupt mapping table for fw */
	ahw->intr_tbl = vzalloc(num_msix *
				sizeof(struct qlcnic_intrpt_config));
	if (!ahw->intr_tbl)
		return -ENOMEM;
	if (!(adapter->flags & QLCNIC_MSIX_ENABLED)) {
		/* MSI-X enablement failed, use legacy interrupt */
		adapter->tgt_status_reg = ahw->pci_base0 + QLC_83XX_INTX_PTR;
		adapter->tgt_mask_reg = ahw->pci_base0 + QLC_83XX_INTX_MASK;
		adapter->isr_int_vec = ahw->pci_base0 + QLC_83XX_INTX_TRGR;
		adapter->msix_entries[0].vector = adapter->pdev->irq;
		dev_info(&adapter->pdev->dev, "using legacy interrupt\n");
	}

	for (i = 0; i < num_msix; i++) {
		if (adapter->flags & QLCNIC_MSIX_ENABLED)
			ahw->intr_tbl[i].type = QLCNIC_INTRPT_MSIX;
		else
			ahw->intr_tbl[i].type = QLCNIC_INTRPT_INTX;
		ahw->intr_tbl[i].id = i;
		ahw->intr_tbl[i].src = 0;
	}
	return 0;
}

inline void qlcnic_83xx_clear_legacy_intr_mask(struct qlcnic_adapter *adapter)
{
	writel(0, adapter->tgt_mask_reg);
}

/* Enable MSI-x and INT-x interrupts */
void qlcnic_83xx_enable_intr(struct qlcnic_adapter *adapter,
			     struct qlcnic_host_sds_ring *sds_ring)
{
	writel(0, sds_ring->crb_intr_mask);
}

/* Disable MSI-x and INT-x interrupts */
void qlcnic_83xx_disable_intr(struct qlcnic_adapter *adapter,
			      struct qlcnic_host_sds_ring *sds_ring)
{
	writel(1, sds_ring->crb_intr_mask);
}

inline void qlcnic_83xx_enable_legacy_msix_mbx_intr(struct qlcnic_adapter
						    *adapter)
{
	u32 mask;

	/* Mailbox in MSI-x mode and Legacy Interrupt share the same
	 * source register. We could be here before contexts are created
	 * and sds_ring->crb_intr_mask has not been initialized, calculate
	 * BAR offset for Interrupt Source Register
	 */
	mask = QLCRDX(adapter->ahw, QLCNIC_DEF_INT_MASK);
	writel(0, adapter->ahw->pci_base0 + mask);
}

void qlcnic_83xx_disable_mbx_intr(struct qlcnic_adapter *adapter)
{
	u32 mask;

	mask = QLCRDX(adapter->ahw, QLCNIC_DEF_INT_MASK);
	writel(1, adapter->ahw->pci_base0 + mask);
	QLCWRX(adapter->ahw, QLCNIC_MBX_INTR_ENBL, 0);
}

static inline void qlcnic_83xx_get_mbx_data(struct qlcnic_adapter *adapter,
				     struct qlcnic_cmd_args *cmd)
{
	int i;
	for (i = 0; i < cmd->rsp.num; i++)
		cmd->rsp.arg[i] = readl(QLCNIC_MBX_FW(adapter->ahw, i));
}

irqreturn_t qlcnic_83xx_clear_legacy_intr(struct qlcnic_adapter *adapter)
{
	u32 intr_val;
	struct qlcnic_hardware_context *ahw = adapter->ahw;
	int retries = 0;

	intr_val = readl(adapter->tgt_status_reg);

	if (!QLC_83XX_VALID_INTX_BIT31(intr_val))
		return IRQ_NONE;

	if (QLC_83XX_INTX_FUNC(intr_val) != adapter->ahw->pci_func) {
		adapter->stats.spurious_intr++;
		return IRQ_NONE;
	}
	/* The barrier is required to ensure writes to the registers */
	wmb();

	/* clear the interrupt trigger control register */
	writel(0, adapter->isr_int_vec);
	intr_val = readl(adapter->isr_int_vec);
	do {
		intr_val = readl(adapter->tgt_status_reg);
		if (QLC_83XX_INTX_FUNC(intr_val) != ahw->pci_func)
			break;
		retries++;
	} while (QLC_83XX_VALID_INTX_BIT30(intr_val) &&
		 (retries < QLC_83XX_LEGACY_INTX_MAX_RETRY));

	return IRQ_HANDLED;
}

static void qlcnic_83xx_poll_process_aen(struct qlcnic_adapter *adapter)
{
	u32 resp, event;
	unsigned long flags;

	spin_lock_irqsave(&adapter->ahw->mbx_lock, flags);

	resp = QLCRDX(adapter->ahw, QLCNIC_FW_MBX_CTRL);
	if (!(resp & QLCNIC_SET_OWNER))
		goto out;

	event = readl(QLCNIC_MBX_FW(adapter->ahw, 0));
	if (event &  QLCNIC_MBX_ASYNC_EVENT)
		__qlcnic_83xx_process_aen(adapter);

out:
	qlcnic_83xx_enable_legacy_msix_mbx_intr(adapter);
	spin_unlock_irqrestore(&adapter->ahw->mbx_lock, flags);
}

irqreturn_t qlcnic_83xx_intr(int irq, void *data)
{
	struct qlcnic_adapter *adapter = data;
	struct qlcnic_host_sds_ring *sds_ring;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	if (qlcnic_83xx_clear_legacy_intr(adapter) == IRQ_NONE)
		return IRQ_NONE;

	qlcnic_83xx_poll_process_aen(adapter);

	if (ahw->diag_test == QLCNIC_INTERRUPT_TEST) {
		ahw->diag_cnt++;
		qlcnic_83xx_enable_legacy_msix_mbx_intr(adapter);
		return IRQ_HANDLED;
	if (!test_bit(__QLCNIC_DEV_UP, &adapter->state)) {
		qlcnic_83xx_enable_legacy_msix_mbx_intr(adapter);
	} else {
		sds_ring = &adapter->recv_ctx->sds_rings[0];
		napi_schedule(&sds_ring->napi);
	}

	return IRQ_HANDLED;
}

irqreturn_t qlcnic_83xx_tmp_intr(int irq, void *data)
{
	struct qlcnic_host_sds_ring *sds_ring = data;
	struct qlcnic_adapter *adapter = sds_ring->adapter;

	if (adapter->flags & QLCNIC_MSIX_ENABLED)
		goto done;

	if (adapter->nic_ops->clear_legacy_intr(adapter) == IRQ_NONE)
		return IRQ_NONE;

done:
	adapter->ahw->diag_cnt++;
	qlcnic_83xx_enable_intr(adapter, sds_ring);

	return IRQ_HANDLED;
}

void qlcnic_83xx_free_mbx_intr(struct qlcnic_adapter *adapter)
{
	u32 num_msix;

	qlcnic_83xx_disable_mbx_intr(adapter);
	if (adapter->flags & QLCNIC_MSIX_ENABLED)
		num_msix = adapter->ahw->num_msix - 1;
	else
		num_msix = 0;
	msleep(20);
	synchronize_irq(adapter->msix_entries[num_msix].vector);
	free_irq(adapter->msix_entries[num_msix].vector, adapter);
}

int qlcnic_83xx_setup_mbx_intr(struct qlcnic_adapter *adapter)
{
	irq_handler_t handler;
	u32 val;
	char name[32];
	int err = 0;
	unsigned long flags = 0;

	if (!(adapter->flags & QLCNIC_MSI_ENABLED) &&
	    !(adapter->flags & QLCNIC_MSIX_ENABLED))
		flags |= IRQF_SHARED;

	if (adapter->flags & QLCNIC_MSIX_ENABLED) {
		handler = qlcnic_83xx_handle_aen;
		val = adapter->msix_entries[adapter->ahw->num_msix - 1].vector;
		snprintf(name, (IFNAMSIZ + 4),
			 "%s[%s]", "qlcnic", "aen");
		err = request_irq(val, handler, flags, name, adapter);
		if (err) {
			dev_err(&adapter->pdev->dev,
				"failed to register MBX interrupt\n");
			return err;
		}
	} else {
		handler = qlcnic_83xx_intr;
		val = adapter->msix_entries[0].vector;
		err = request_irq(val, handler, flags, "qlcnic", adapter);
		if (err) {
			dev_err(&adapter->pdev->dev,
				"failed to register INTx interrupt\n");
			return err;
		}
		qlcnic_83xx_clear_legacy_intr_mask(adapter);
	}

	/* Enable mailbox interrupt */
	qlcnic_83xx_enable_mbx_intrpt(adapter);

	return err;
}

void qlcnic_83xx_get_func_no(struct qlcnic_adapter *adapter)
{
	u32 val = QLCRDX(adapter->ahw, QLCNIC_INFORMANT);
Rajesh Borundia's avatar
Rajesh Borundia committed
	adapter->ahw->pci_func = (val >> 24) & 0xff;
}

int qlcnic_83xx_cam_lock(struct qlcnic_adapter *adapter)
{
	void __iomem *addr;
	u32 val, limit = 0;

	struct qlcnic_hardware_context *ahw = adapter->ahw;

	addr = ahw->pci_base0 + QLC_83XX_SEM_LOCK_FUNC(ahw->pci_func);
	do {
		val = readl(addr);
		if (val) {
			/* write the function number to register */
			QLC_SHARED_REG_WR32(adapter, QLCNIC_FLASH_LOCK_OWNER,
					    ahw->pci_func);
			return 0;
		}
		usleep_range(1000, 2000);
	} while (++limit <= QLCNIC_PCIE_SEM_TIMEOUT);

	return -EIO;
}

void qlcnic_83xx_cam_unlock(struct qlcnic_adapter *adapter)
{
	void __iomem *addr;
	u32 val;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	addr = ahw->pci_base0 + QLC_83XX_SEM_UNLOCK_FUNC(ahw->pci_func);
	val = readl(addr);
}

void qlcnic_83xx_read_crb(struct qlcnic_adapter *adapter, char *buf,
			  loff_t offset, size_t size)
{
	int ret;
	u32 data;

	if (qlcnic_api_lock(adapter)) {
		dev_err(&adapter->pdev->dev,
			"%s: failed to acquire lock. addr offset 0x%x\n",
			__func__, (u32)offset);
		return;
	}

	ret = qlcnic_83xx_rd_reg_indirect(adapter, (u32) offset);
	qlcnic_api_unlock(adapter);

	if (ret == -EIO) {
		dev_err(&adapter->pdev->dev,
			"%s: failed. addr offset 0x%x\n",
			__func__, (u32)offset);
		return;
	}
	data = ret;
	memcpy(buf, &data, size);
}

void qlcnic_83xx_write_crb(struct qlcnic_adapter *adapter, char *buf,
			   loff_t offset, size_t size)
{
	u32 data;

	memcpy(&data, buf, size);
	qlcnic_83xx_wrt_reg_indirect(adapter, (u32) offset, data);
}

int qlcnic_83xx_get_port_info(struct qlcnic_adapter *adapter)
{
	int status;

	status = qlcnic_83xx_get_port_config(adapter);
	if (status) {
		dev_err(&adapter->pdev->dev,
			"Get Port Info failed\n");
	} else {
		if (QLC_83XX_SFP_10G_CAPABLE(adapter->ahw->port_config))
			adapter->ahw->port_type = QLCNIC_XGBE;
		else
			adapter->ahw->port_type = QLCNIC_GBE;
		if (QLC_83XX_AUTONEG(adapter->ahw->port_config))
			adapter->ahw->link_autoneg = AUTONEG_ENABLE;
	}
	return status;
}

void qlcnic_83xx_enable_mbx_intrpt(struct qlcnic_adapter *adapter)
{
	u32 val;

	if (adapter->flags & QLCNIC_MSIX_ENABLED)
		val = BIT_2 | ((adapter->ahw->num_msix - 1) << 8);
	else
		val = BIT_2;
	QLCWRX(adapter->ahw, QLCNIC_MBX_INTR_ENBL, val);
	qlcnic_83xx_enable_legacy_msix_mbx_intr(adapter);
}

void qlcnic_83xx_check_vf(struct qlcnic_adapter *adapter,
			  const struct pci_device_id *ent)
{
	u32 op_mode, priv_level;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	ahw->fw_hal_version = 2;
	qlcnic_get_func_no(adapter);

Rajesh Borundia's avatar
Rajesh Borundia committed
	if (qlcnic_sriov_vf_check(adapter)) {
		qlcnic_sriov_vf_set_ops(adapter);
		return;
	}

	/* Determine function privilege level */
	op_mode = QLCRDX(adapter->ahw, QLC_83XX_DRV_OP_MODE);
	if (op_mode == QLC_83XX_DEFAULT_OPMODE)
		priv_level = QLCNIC_MGMT_FUNC;
	else
		priv_level = QLC_83XX_GET_FUNC_PRIVILEGE(op_mode,
							 ahw->pci_func);

	if (priv_level == QLCNIC_NON_PRIV_FUNC) {
		ahw->op_mode = QLCNIC_NON_PRIV_FUNC;
		dev_info(&adapter->pdev->dev,
			 "HAL Version: %d Non Privileged function\n",
			 ahw->fw_hal_version);
		adapter->nic_ops = &qlcnic_vf_ops;
	} else {
		if (pci_find_ext_capability(adapter->pdev,
					    PCI_EXT_CAP_ID_SRIOV))
			set_bit(__QLCNIC_SRIOV_CAPABLE, &adapter->state);
		adapter->nic_ops = &qlcnic_83xx_ops;
	}
}

static void qlcnic_83xx_handle_link_aen(struct qlcnic_adapter *adapter,
					u32 data[]);
static void qlcnic_83xx_handle_idc_comp_aen(struct qlcnic_adapter *adapter,
					    u32 data[]);

static void qlcnic_dump_mbx(struct qlcnic_adapter *adapter,
			    struct qlcnic_cmd_args *cmd)
{
	int i;

	dev_info(&adapter->pdev->dev,
		 "Host MBX regs(%d)\n", cmd->req.num);
	for (i = 0; i < cmd->req.num; i++) {
		if (i && !(i % 8))
			pr_info("\n");
		pr_info("%08x ", cmd->req.arg[i]);
	}
	pr_info("\n");
	dev_info(&adapter->pdev->dev,
		 "FW MBX regs(%d)\n", cmd->rsp.num);
	for (i = 0; i < cmd->rsp.num; i++) {
		if (i && !(i % 8))
			pr_info("\n");
		pr_info("%08x ", cmd->rsp.arg[i]);
	}
	pr_info("\n");
}

/* Mailbox response for mac rcode */
u32 qlcnic_83xx_mac_rcode(struct qlcnic_adapter *adapter)
{
	u32 fw_data;
	u8 mac_cmd_rcode;

	fw_data = readl(QLCNIC_MBX_FW(adapter->ahw, 2));
	mac_cmd_rcode = (u8)fw_data;
	if (mac_cmd_rcode == QLC_83XX_NO_NIC_RESOURCE ||
	    mac_cmd_rcode == QLC_83XX_MAC_PRESENT ||
	    mac_cmd_rcode == QLC_83XX_MAC_ABSENT)
		return QLCNIC_RCODE_SUCCESS;
	return 1;
}

u32 qlcnic_83xx_mbx_poll(struct qlcnic_adapter *adapter)
{
	u32 data;
	unsigned long wait_time = 0;
	struct qlcnic_hardware_context *ahw = adapter->ahw;
	/* wait for mailbox completion */
	do {
		data = QLCRDX(ahw, QLCNIC_FW_MBX_CTRL);
		if (++wait_time > QLCNIC_MBX_TIMEOUT) {
			data = QLCNIC_RCODE_TIMEOUT;
			break;
		}
		mdelay(1);
	} while (!data);
	return data;
}

int qlcnic_83xx_mbx_op(struct qlcnic_adapter *adapter,
		       struct qlcnic_cmd_args *cmd)
{
	int i;
	u16 opcode;
	u8 mbx_err_code;
	u32 rsp, mbx_val, fw_data, rsp_num, mbx_cmd;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	opcode = LSW(cmd->req.arg[0]);
	if (!test_bit(QLC_83XX_MBX_READY, &adapter->ahw->idc.status)) {
		dev_info(&adapter->pdev->dev,
			 "Mailbox cmd attempted, 0x%x\n", opcode);
		dev_info(&adapter->pdev->dev, "Mailbox detached\n");
		return 0;
	}

	spin_lock_irqsave(&adapter->ahw->mbx_lock, flags);
	mbx_val = QLCRDX(ahw, QLCNIC_HOST_MBX_CTRL);

	if (mbx_val) {
		QLCDB(adapter, DRV,
		      "Mailbox cmd attempted, 0x%x\n", opcode);
		QLCDB(adapter, DRV,
		      "Mailbox not available, 0x%x, collect FW dump\n",
		      mbx_val);
		cmd->rsp.arg[0] = QLCNIC_RCODE_TIMEOUT;
		spin_unlock_irqrestore(&adapter->ahw->mbx_lock, flags);
		return cmd->rsp.arg[0];
	}

	/* Fill in mailbox registers */
	mbx_cmd = cmd->req.arg[0];
	writel(mbx_cmd, QLCNIC_MBX_HOST(ahw, 0));
	for (i = 1; i < cmd->req.num; i++)
		writel(cmd->req.arg[i], QLCNIC_MBX_HOST(ahw, i));

	/* Signal FW about the impending command */
	QLCWRX(ahw, QLCNIC_HOST_MBX_CTRL, QLCNIC_SET_OWNER);
poll:
	rsp = qlcnic_83xx_mbx_poll(adapter);
	if (rsp != QLCNIC_RCODE_TIMEOUT) {
		/* Get the FW response data */
		fw_data = readl(QLCNIC_MBX_FW(ahw, 0));
		if (fw_data &  QLCNIC_MBX_ASYNC_EVENT) {
			__qlcnic_83xx_process_aen(adapter);
			mbx_val = QLCRDX(ahw, QLCNIC_HOST_MBX_CTRL);
			if (mbx_val)
				goto poll;
		}
		mbx_err_code = QLCNIC_MBX_STATUS(fw_data);
		rsp_num = QLCNIC_MBX_NUM_REGS(fw_data);
		opcode = QLCNIC_MBX_RSP(fw_data);
		qlcnic_83xx_get_mbx_data(adapter, cmd);

		switch (mbx_err_code) {
		case QLCNIC_MBX_RSP_OK:
		case QLCNIC_MBX_PORT_RSP_OK:
			rsp = QLCNIC_RCODE_SUCCESS;
			if (opcode == QLCNIC_CMD_CONFIG_MAC_VLAN) {
				rsp = qlcnic_83xx_mac_rcode(adapter);
				if (!rsp)
			dev_err(&adapter->pdev->dev,
				"MBX command 0x%x failed with err:0x%x\n",
				opcode, mbx_err_code);
			rsp = mbx_err_code;
			qlcnic_dump_mbx(adapter, cmd);

	dev_err(&adapter->pdev->dev, "MBX command 0x%x timed out\n",
		QLCNIC_MBX_RSP(mbx_cmd));
	rsp = QLCNIC_RCODE_TIMEOUT;
out:
	/* clear fw mbx control register */
	QLCWRX(ahw, QLCNIC_FW_MBX_CTRL, QLCNIC_CLR_OWNER);
	spin_unlock_irqrestore(&adapter->ahw->mbx_lock, flags);
	return rsp;
}

int qlcnic_83xx_alloc_mbx_args(struct qlcnic_cmd_args *mbx,
			       struct qlcnic_adapter *adapter, u32 type)
{
	int i, size;
	u32 temp;
	const struct qlcnic_mailbox_metadata *mbx_tbl;

	mbx_tbl = qlcnic_83xx_mbx_tbl;
	size = ARRAY_SIZE(qlcnic_83xx_mbx_tbl);
	for (i = 0; i < size; i++) {
		if (type == mbx_tbl[i].cmd) {
			mbx->op_type = QLC_83XX_FW_MBX_CMD;
			mbx->req.num = mbx_tbl[i].in_args;
			mbx->rsp.num = mbx_tbl[i].out_args;
			mbx->req.arg = kcalloc(mbx->req.num, sizeof(u32),
					       GFP_ATOMIC);
			if (!mbx->req.arg)
				return -ENOMEM;
			mbx->rsp.arg = kcalloc(mbx->rsp.num, sizeof(u32),
					       GFP_ATOMIC);
			if (!mbx->rsp.arg) {
				kfree(mbx->req.arg);
				mbx->req.arg = NULL;
				return -ENOMEM;
			}
			memset(mbx->req.arg, 0, sizeof(u32) * mbx->req.num);
			memset(mbx->rsp.arg, 0, sizeof(u32) * mbx->rsp.num);
			temp = adapter->ahw->fw_hal_version << 29;
			mbx->req.arg[0] = (type | (mbx->req.num << 16) | temp);
}

void qlcnic_83xx_idc_aen_work(struct work_struct *work)
{
	struct qlcnic_adapter *adapter;
	struct qlcnic_cmd_args cmd;
	int i, err = 0;

	adapter = container_of(work, struct qlcnic_adapter, idc_aen_work.work);
	qlcnic_alloc_mbx_args(&cmd, adapter, QLCNIC_CMD_IDC_ACK);

	for (i = 1; i < QLC_83XX_MBX_AEN_CNT; i++)
		cmd.req.arg[i] = adapter->ahw->mbox_aen[i];

	err = qlcnic_issue_cmd(adapter, &cmd);
	if (err)
		dev_info(&adapter->pdev->dev,
			 "%s: Mailbox IDC ACK failed.\n", __func__);
	qlcnic_free_mbx_args(&cmd);
}

static void qlcnic_83xx_handle_idc_comp_aen(struct qlcnic_adapter *adapter,
					    u32 data[])
{
	dev_dbg(&adapter->pdev->dev, "Completion AEN:0x%x.\n",
		QLCNIC_MBX_RSP(data[0]));
	clear_bit(QLC_83XX_IDC_COMP_AEN, &adapter->ahw->idc.status);
void __qlcnic_83xx_process_aen(struct qlcnic_adapter *adapter)
	u32 event[QLC_83XX_MBX_AEN_CNT];
	int i;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	for (i = 0; i < QLC_83XX_MBX_AEN_CNT; i++)
		event[i] = readl(QLCNIC_MBX_FW(ahw, i));

	switch (QLCNIC_MBX_RSP(event[0])) {

	case QLCNIC_MBX_LINK_EVENT:
		qlcnic_83xx_handle_link_aen(adapter, event);
		break;
	case QLCNIC_MBX_COMP_EVENT:
		qlcnic_83xx_handle_idc_comp_aen(adapter, event);
		break;
	case QLCNIC_MBX_REQUEST_EVENT:
		for (i = 0; i < QLC_83XX_MBX_AEN_CNT; i++)
			adapter->ahw->mbox_aen[i] = QLCNIC_MBX_RSP(event[i]);
		queue_delayed_work(adapter->qlcnic_wq,
				   &adapter->idc_aen_work, 0);
		break;
	case QLCNIC_MBX_TIME_EXTEND_EVENT:
		break;
	case QLCNIC_MBX_BC_EVENT:
		qlcnic_sriov_handle_bc_event(adapter, event[1]);
		break;
	case QLCNIC_MBX_SFP_INSERT_EVENT:
		dev_info(&adapter->pdev->dev, "SFP+ Insert AEN:0x%x.\n",
			 QLCNIC_MBX_RSP(event[0]));
		break;
	case QLCNIC_MBX_SFP_REMOVE_EVENT:
		dev_info(&adapter->pdev->dev, "SFP Removed AEN:0x%x.\n",
			 QLCNIC_MBX_RSP(event[0]));
		break;
	default:
		dev_dbg(&adapter->pdev->dev, "Unsupported AEN:0x%x.\n",
			QLCNIC_MBX_RSP(event[0]));
		break;
	}

	QLCWRX(ahw, QLCNIC_FW_MBX_CTRL, QLCNIC_CLR_OWNER);
}

static void qlcnic_83xx_process_aen(struct qlcnic_adapter *adapter)
{
	struct qlcnic_hardware_context *ahw = adapter->ahw;
	u32 resp, event;
	unsigned long flags;

	spin_lock_irqsave(&ahw->mbx_lock, flags);

	resp = QLCRDX(ahw, QLCNIC_FW_MBX_CTRL);
	if (resp & QLCNIC_SET_OWNER) {
		event = readl(QLCNIC_MBX_FW(ahw, 0));
		if (event &  QLCNIC_MBX_ASYNC_EVENT)
			__qlcnic_83xx_process_aen(adapter);
	}

	spin_unlock_irqrestore(&ahw->mbx_lock, flags);
}

static void qlcnic_83xx_mbx_poll_work(struct work_struct *work)
{
	struct qlcnic_adapter *adapter;

	adapter = container_of(work, struct qlcnic_adapter, mbx_poll_work.work);

	if (!test_bit(__QLCNIC_MBX_POLL_ENABLE, &adapter->state))
		return;

	qlcnic_83xx_process_aen(adapter);
	queue_delayed_work(adapter->qlcnic_wq, &adapter->mbx_poll_work,
			   (HZ / 10));
}

void qlcnic_83xx_enable_mbx_poll(struct qlcnic_adapter *adapter)
{
	if (test_and_set_bit(__QLCNIC_MBX_POLL_ENABLE, &adapter->state))
		return;

	INIT_DELAYED_WORK(&adapter->mbx_poll_work, qlcnic_83xx_mbx_poll_work);
}

void qlcnic_83xx_disable_mbx_poll(struct qlcnic_adapter *adapter)
{
	if (!test_and_clear_bit(__QLCNIC_MBX_POLL_ENABLE, &adapter->state))
		return;
	cancel_delayed_work_sync(&adapter->mbx_poll_work);
}

static int qlcnic_83xx_add_rings(struct qlcnic_adapter *adapter)
{
	int index, i, err, sds_mbx_size;
	u32 *buf, intrpt_id, intr_mask;
	u16 context_id;
	u8 num_sds;
	struct qlcnic_cmd_args cmd;
	struct qlcnic_host_sds_ring *sds;
	struct qlcnic_sds_mbx sds_mbx;
	struct qlcnic_add_rings_mbx_out *mbx_out;
	struct qlcnic_recv_context *recv_ctx = adapter->recv_ctx;
	struct qlcnic_hardware_context *ahw = adapter->ahw;

	sds_mbx_size = sizeof(struct qlcnic_sds_mbx);
	context_id = recv_ctx->context_id;
	num_sds = (adapter->max_sds_rings - QLCNIC_MAX_RING_SETS);
	ahw->hw_ops->alloc_mbx_args(&cmd, adapter,
				    QLCNIC_CMD_ADD_RCV_RINGS);
	cmd.req.arg[1] = 0 | (num_sds << 8) | (context_id << 16);

	/* set up status rings, mbx 2-81 */
	index = 2;
	for (i = 8; i < adapter->max_sds_rings; i++) {
		memset(&sds_mbx, 0, sds_mbx_size);
		sds = &recv_ctx->sds_rings[i];
		sds->consumer = 0;
		memset(sds->desc_head, 0, STATUS_DESC_RINGSIZE(sds));
		sds_mbx.phy_addr_low = LSD(sds->phys_addr);
		sds_mbx.phy_addr_high = MSD(sds->phys_addr);
		sds_mbx.sds_ring_size = sds->num_desc;

		if (adapter->flags & QLCNIC_MSIX_ENABLED)
			intrpt_id = ahw->intr_tbl[i].id;
		else
			intrpt_id = QLCRDX(ahw, QLCNIC_DEF_INT_ID);

		if (adapter->ahw->diag_test != QLCNIC_LOOPBACK_TEST)
			sds_mbx.intrpt_id = intrpt_id;
		else
			sds_mbx.intrpt_id = 0xffff;
		sds_mbx.intrpt_val = 0;
		buf = &cmd.req.arg[index];
		memcpy(buf, &sds_mbx, sds_mbx_size);