| /* bnx2x_main.c: QLogic Everest network driver. |
| * |
| * Copyright (c) 2007-2013 Broadcom Corporation |
| * Copyright (c) 2014 QLogic Corporation |
| * All rights reserved |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation. |
| * |
| * Maintained by: Ariel Elior <ariel.elior@qlogic.com> |
| * Written by: Eliezer Tamir |
| * Based on code from Michael Chan's bnx2 driver |
| * UDP CSUM errata workaround by Arik Gendelman |
| * Slowpath and fastpath rework by Vladislav Zolotarov |
| * Statistics and Link management by Yitchak Gertner |
| * |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/kernel.h> |
| #include <linux/device.h> /* for dev_info() */ |
| #include <linux/timer.h> |
| #include <linux/errno.h> |
| #include <linux/ioport.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/pci.h> |
| #include <linux/aer.h> |
| #include <linux/init.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/skbuff.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/bitops.h> |
| #include <linux/irq.h> |
| #include <linux/delay.h> |
| #include <asm/byteorder.h> |
| #include <linux/time.h> |
| #include <linux/ethtool.h> |
| #include <linux/mii.h> |
| #include <linux/if_vlan.h> |
| #include <linux/crash_dump.h> |
| #include <net/ip.h> |
| #include <net/ipv6.h> |
| #include <net/tcp.h> |
| #include <net/vxlan.h> |
| #include <net/checksum.h> |
| #include <net/ip6_checksum.h> |
| #include <linux/workqueue.h> |
| #include <linux/crc32.h> |
| #include <linux/crc32c.h> |
| #include <linux/prefetch.h> |
| #include <linux/zlib.h> |
| #include <linux/io.h> |
| #include <linux/semaphore.h> |
| #include <linux/stringify.h> |
| #include <linux/vmalloc.h> |
| #include "bnx2x.h" |
| #include "bnx2x_init.h" |
| #include "bnx2x_init_ops.h" |
| #include "bnx2x_cmn.h" |
| #include "bnx2x_vfpf.h" |
| #include "bnx2x_dcb.h" |
| #include "bnx2x_sp.h" |
| #include <linux/firmware.h> |
| #include "bnx2x_fw_file_hdr.h" |
| /* FW files */ |
| #define FW_FILE_VERSION \ |
| __stringify(BCM_5710_FW_MAJOR_VERSION) "." \ |
| __stringify(BCM_5710_FW_MINOR_VERSION) "." \ |
| __stringify(BCM_5710_FW_REVISION_VERSION) "." \ |
| __stringify(BCM_5710_FW_ENGINEERING_VERSION) |
| |
| #define FW_FILE_VERSION_V15 \ |
| __stringify(BCM_5710_FW_MAJOR_VERSION) "." \ |
| __stringify(BCM_5710_FW_MINOR_VERSION) "." \ |
| __stringify(BCM_5710_FW_REVISION_VERSION_V15) "." \ |
| __stringify(BCM_5710_FW_ENGINEERING_VERSION) |
| |
| #define FW_FILE_NAME_E1 "bnx2x/bnx2x-e1-" FW_FILE_VERSION ".fw" |
| #define FW_FILE_NAME_E1H "bnx2x/bnx2x-e1h-" FW_FILE_VERSION ".fw" |
| #define FW_FILE_NAME_E2 "bnx2x/bnx2x-e2-" FW_FILE_VERSION ".fw" |
| #define FW_FILE_NAME_E1_V15 "bnx2x/bnx2x-e1-" FW_FILE_VERSION_V15 ".fw" |
| #define FW_FILE_NAME_E1H_V15 "bnx2x/bnx2x-e1h-" FW_FILE_VERSION_V15 ".fw" |
| #define FW_FILE_NAME_E2_V15 "bnx2x/bnx2x-e2-" FW_FILE_VERSION_V15 ".fw" |
| |
| /* Time in jiffies before concluding the transmitter is hung */ |
| #define TX_TIMEOUT (5*HZ) |
| |
| MODULE_AUTHOR("Eliezer Tamir"); |
| MODULE_DESCRIPTION("QLogic " |
| "BCM57710/57711/57711E/" |
| "57712/57712_MF/57800/57800_MF/57810/57810_MF/" |
| "57840/57840_MF Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_FIRMWARE(FW_FILE_NAME_E1); |
| MODULE_FIRMWARE(FW_FILE_NAME_E1H); |
| MODULE_FIRMWARE(FW_FILE_NAME_E2); |
| |
| int bnx2x_num_queues; |
| module_param_named(num_queues, bnx2x_num_queues, int, 0444); |
| MODULE_PARM_DESC(num_queues, |
| " Set number of queues (default is as a number of CPUs)"); |
| |
| static int disable_tpa; |
| module_param(disable_tpa, int, 0444); |
| MODULE_PARM_DESC(disable_tpa, " Disable the TPA (LRO) feature"); |
| |
| static int int_mode; |
| module_param(int_mode, int, 0444); |
| MODULE_PARM_DESC(int_mode, " Force interrupt mode other than MSI-X " |
| "(1 INT#x; 2 MSI)"); |
| |
| static int dropless_fc; |
| module_param(dropless_fc, int, 0444); |
| MODULE_PARM_DESC(dropless_fc, " Pause on exhausted host ring"); |
| |
| static int mrrs = -1; |
| module_param(mrrs, int, 0444); |
| MODULE_PARM_DESC(mrrs, " Force Max Read Req Size (0..3) (for debug)"); |
| |
| static int debug; |
| module_param(debug, int, 0444); |
| MODULE_PARM_DESC(debug, " Default debug msglevel"); |
| |
| static struct workqueue_struct *bnx2x_wq; |
| struct workqueue_struct *bnx2x_iov_wq; |
| |
| struct bnx2x_mac_vals { |
| u32 xmac_addr; |
| u32 xmac_val; |
| u32 emac_addr; |
| u32 emac_val; |
| u32 umac_addr[2]; |
| u32 umac_val[2]; |
| u32 bmac_addr; |
| u32 bmac_val[2]; |
| }; |
| |
| enum bnx2x_board_type { |
| BCM57710 = 0, |
| BCM57711, |
| BCM57711E, |
| BCM57712, |
| BCM57712_MF, |
| BCM57712_VF, |
| BCM57800, |
| BCM57800_MF, |
| BCM57800_VF, |
| BCM57810, |
| BCM57810_MF, |
| BCM57810_VF, |
| BCM57840_4_10, |
| BCM57840_2_20, |
| BCM57840_MF, |
| BCM57840_VF, |
| BCM57811, |
| BCM57811_MF, |
| BCM57840_O, |
| BCM57840_MFO, |
| BCM57811_VF |
| }; |
| |
| /* indexed by board_type, above */ |
| static struct { |
| char *name; |
| } board_info[] = { |
| [BCM57710] = { "QLogic BCM57710 10 Gigabit PCIe [Everest]" }, |
| [BCM57711] = { "QLogic BCM57711 10 Gigabit PCIe" }, |
| [BCM57711E] = { "QLogic BCM57711E 10 Gigabit PCIe" }, |
| [BCM57712] = { "QLogic BCM57712 10 Gigabit Ethernet" }, |
| [BCM57712_MF] = { "QLogic BCM57712 10 Gigabit Ethernet Multi Function" }, |
| [BCM57712_VF] = { "QLogic BCM57712 10 Gigabit Ethernet Virtual Function" }, |
| [BCM57800] = { "QLogic BCM57800 10 Gigabit Ethernet" }, |
| [BCM57800_MF] = { "QLogic BCM57800 10 Gigabit Ethernet Multi Function" }, |
| [BCM57800_VF] = { "QLogic BCM57800 10 Gigabit Ethernet Virtual Function" }, |
| [BCM57810] = { "QLogic BCM57810 10 Gigabit Ethernet" }, |
| [BCM57810_MF] = { "QLogic BCM57810 10 Gigabit Ethernet Multi Function" }, |
| [BCM57810_VF] = { "QLogic BCM57810 10 Gigabit Ethernet Virtual Function" }, |
| [BCM57840_4_10] = { "QLogic BCM57840 10 Gigabit Ethernet" }, |
| [BCM57840_2_20] = { "QLogic BCM57840 20 Gigabit Ethernet" }, |
| [BCM57840_MF] = { "QLogic BCM57840 10/20 Gigabit Ethernet Multi Function" }, |
| [BCM57840_VF] = { "QLogic BCM57840 10/20 Gigabit Ethernet Virtual Function" }, |
| [BCM57811] = { "QLogic BCM57811 10 Gigabit Ethernet" }, |
| [BCM57811_MF] = { "QLogic BCM57811 10 Gigabit Ethernet Multi Function" }, |
| [BCM57840_O] = { "QLogic BCM57840 10/20 Gigabit Ethernet" }, |
| [BCM57840_MFO] = { "QLogic BCM57840 10/20 Gigabit Ethernet Multi Function" }, |
| [BCM57811_VF] = { "QLogic BCM57840 10/20 Gigabit Ethernet Virtual Function" } |
| }; |
| |
| #ifndef PCI_DEVICE_ID_NX2_57710 |
| #define PCI_DEVICE_ID_NX2_57710 CHIP_NUM_57710 |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57711 |
| #define PCI_DEVICE_ID_NX2_57711 CHIP_NUM_57711 |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57711E |
| #define PCI_DEVICE_ID_NX2_57711E CHIP_NUM_57711E |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57712 |
| #define PCI_DEVICE_ID_NX2_57712 CHIP_NUM_57712 |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57712_MF |
| #define PCI_DEVICE_ID_NX2_57712_MF CHIP_NUM_57712_MF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57712_VF |
| #define PCI_DEVICE_ID_NX2_57712_VF CHIP_NUM_57712_VF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57800 |
| #define PCI_DEVICE_ID_NX2_57800 CHIP_NUM_57800 |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57800_MF |
| #define PCI_DEVICE_ID_NX2_57800_MF CHIP_NUM_57800_MF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57800_VF |
| #define PCI_DEVICE_ID_NX2_57800_VF CHIP_NUM_57800_VF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57810 |
| #define PCI_DEVICE_ID_NX2_57810 CHIP_NUM_57810 |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57810_MF |
| #define PCI_DEVICE_ID_NX2_57810_MF CHIP_NUM_57810_MF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57840_O |
| #define PCI_DEVICE_ID_NX2_57840_O CHIP_NUM_57840_OBSOLETE |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57810_VF |
| #define PCI_DEVICE_ID_NX2_57810_VF CHIP_NUM_57810_VF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57840_4_10 |
| #define PCI_DEVICE_ID_NX2_57840_4_10 CHIP_NUM_57840_4_10 |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57840_2_20 |
| #define PCI_DEVICE_ID_NX2_57840_2_20 CHIP_NUM_57840_2_20 |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57840_MFO |
| #define PCI_DEVICE_ID_NX2_57840_MFO CHIP_NUM_57840_MF_OBSOLETE |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57840_MF |
| #define PCI_DEVICE_ID_NX2_57840_MF CHIP_NUM_57840_MF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57840_VF |
| #define PCI_DEVICE_ID_NX2_57840_VF CHIP_NUM_57840_VF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57811 |
| #define PCI_DEVICE_ID_NX2_57811 CHIP_NUM_57811 |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57811_MF |
| #define PCI_DEVICE_ID_NX2_57811_MF CHIP_NUM_57811_MF |
| #endif |
| #ifndef PCI_DEVICE_ID_NX2_57811_VF |
| #define PCI_DEVICE_ID_NX2_57811_VF CHIP_NUM_57811_VF |
| #endif |
| |
| static const struct pci_device_id bnx2x_pci_tbl[] = { |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57710), BCM57710 }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57711), BCM57711 }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57711E), BCM57711E }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57712), BCM57712 }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57712_MF), BCM57712_MF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57712_VF), BCM57712_VF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57800), BCM57800 }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57800_MF), BCM57800_MF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57800_VF), BCM57800_VF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57810), BCM57810 }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57810_MF), BCM57810_MF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57840_O), BCM57840_O }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57840_4_10), BCM57840_4_10 }, |
| { PCI_VDEVICE(QLOGIC, PCI_DEVICE_ID_NX2_57840_4_10), BCM57840_4_10 }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57840_2_20), BCM57840_2_20 }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57810_VF), BCM57810_VF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57840_MFO), BCM57840_MFO }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57840_MF), BCM57840_MF }, |
| { PCI_VDEVICE(QLOGIC, PCI_DEVICE_ID_NX2_57840_MF), BCM57840_MF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57840_VF), BCM57840_VF }, |
| { PCI_VDEVICE(QLOGIC, PCI_DEVICE_ID_NX2_57840_VF), BCM57840_VF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57811), BCM57811 }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57811_MF), BCM57811_MF }, |
| { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57811_VF), BCM57811_VF }, |
| { 0 } |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, bnx2x_pci_tbl); |
| |
| const u32 dmae_reg_go_c[] = { |
| DMAE_REG_GO_C0, DMAE_REG_GO_C1, DMAE_REG_GO_C2, DMAE_REG_GO_C3, |
| DMAE_REG_GO_C4, DMAE_REG_GO_C5, DMAE_REG_GO_C6, DMAE_REG_GO_C7, |
| DMAE_REG_GO_C8, DMAE_REG_GO_C9, DMAE_REG_GO_C10, DMAE_REG_GO_C11, |
| DMAE_REG_GO_C12, DMAE_REG_GO_C13, DMAE_REG_GO_C14, DMAE_REG_GO_C15 |
| }; |
| |
| /* Global resources for unloading a previously loaded device */ |
| #define BNX2X_PREV_WAIT_NEEDED 1 |
| static DEFINE_SEMAPHORE(bnx2x_prev_sem); |
| static LIST_HEAD(bnx2x_prev_list); |
| |
| /* Forward declaration */ |
| static struct cnic_eth_dev *bnx2x_cnic_probe(struct net_device *dev); |
| static u32 bnx2x_rx_ustorm_prods_offset(struct bnx2x_fastpath *fp); |
| static int bnx2x_set_storm_rx_mode(struct bnx2x *bp); |
| |
| /**************************************************************************** |
| * General service functions |
| ****************************************************************************/ |
| |
| static int bnx2x_hwtstamp_ioctl(struct bnx2x *bp, struct ifreq *ifr); |
| |
| static void __storm_memset_dma_mapping(struct bnx2x *bp, |
| u32 addr, dma_addr_t mapping) |
| { |
| REG_WR(bp, addr, U64_LO(mapping)); |
| REG_WR(bp, addr + 4, U64_HI(mapping)); |
| } |
| |
| static void storm_memset_spq_addr(struct bnx2x *bp, |
| dma_addr_t mapping, u16 abs_fid) |
| { |
| u32 addr = XSEM_REG_FAST_MEMORY + |
| XSTORM_SPQ_PAGE_BASE_OFFSET(abs_fid); |
| |
| __storm_memset_dma_mapping(bp, addr, mapping); |
| } |
| |
| static void storm_memset_vf_to_pf(struct bnx2x *bp, u16 abs_fid, |
| u16 pf_id) |
| { |
| REG_WR8(bp, BAR_XSTRORM_INTMEM + XSTORM_VF_TO_PF_OFFSET(abs_fid), |
| pf_id); |
| REG_WR8(bp, BAR_CSTRORM_INTMEM + CSTORM_VF_TO_PF_OFFSET(abs_fid), |
| pf_id); |
| REG_WR8(bp, BAR_TSTRORM_INTMEM + TSTORM_VF_TO_PF_OFFSET(abs_fid), |
| pf_id); |
| REG_WR8(bp, BAR_USTRORM_INTMEM + USTORM_VF_TO_PF_OFFSET(abs_fid), |
| pf_id); |
| } |
| |
| static void storm_memset_func_en(struct bnx2x *bp, u16 abs_fid, |
| u8 enable) |
| { |
| REG_WR8(bp, BAR_XSTRORM_INTMEM + XSTORM_FUNC_EN_OFFSET(abs_fid), |
| enable); |
| REG_WR8(bp, BAR_CSTRORM_INTMEM + CSTORM_FUNC_EN_OFFSET(abs_fid), |
| enable); |
| REG_WR8(bp, BAR_TSTRORM_INTMEM + TSTORM_FUNC_EN_OFFSET(abs_fid), |
| enable); |
| REG_WR8(bp, BAR_USTRORM_INTMEM + USTORM_FUNC_EN_OFFSET(abs_fid), |
| enable); |
| } |
| |
| static void storm_memset_eq_data(struct bnx2x *bp, |
| struct event_ring_data *eq_data, |
| u16 pfid) |
| { |
| size_t size = sizeof(struct event_ring_data); |
| |
| u32 addr = BAR_CSTRORM_INTMEM + CSTORM_EVENT_RING_DATA_OFFSET(pfid); |
| |
| __storm_memset_struct(bp, addr, size, (u32 *)eq_data); |
| } |
| |
| static void storm_memset_eq_prod(struct bnx2x *bp, u16 eq_prod, |
| u16 pfid) |
| { |
| u32 addr = BAR_CSTRORM_INTMEM + CSTORM_EVENT_RING_PROD_OFFSET(pfid); |
| REG_WR16(bp, addr, eq_prod); |
| } |
| |
| /* used only at init |
| * locking is done by mcp |
| */ |
| static void bnx2x_reg_wr_ind(struct bnx2x *bp, u32 addr, u32 val) |
| { |
| pci_write_config_dword(bp->pdev, PCICFG_GRC_ADDRESS, addr); |
| pci_write_config_dword(bp->pdev, PCICFG_GRC_DATA, val); |
| pci_write_config_dword(bp->pdev, PCICFG_GRC_ADDRESS, |
| PCICFG_VENDOR_ID_OFFSET); |
| } |
| |
| static u32 bnx2x_reg_rd_ind(struct bnx2x *bp, u32 addr) |
| { |
| u32 val; |
| |
| pci_write_config_dword(bp->pdev, PCICFG_GRC_ADDRESS, addr); |
| pci_read_config_dword(bp->pdev, PCICFG_GRC_DATA, &val); |
| pci_write_config_dword(bp->pdev, PCICFG_GRC_ADDRESS, |
| PCICFG_VENDOR_ID_OFFSET); |
| |
| return val; |
| } |
| |
| #define DMAE_DP_SRC_GRC "grc src_addr [%08x]" |
| #define DMAE_DP_SRC_PCI "pci src_addr [%x:%08x]" |
| #define DMAE_DP_DST_GRC "grc dst_addr [%08x]" |
| #define DMAE_DP_DST_PCI "pci dst_addr [%x:%08x]" |
| #define DMAE_DP_DST_NONE "dst_addr [none]" |
| |
| static void bnx2x_dp_dmae(struct bnx2x *bp, |
| struct dmae_command *dmae, int msglvl) |
| { |
| u32 src_type = dmae->opcode & DMAE_COMMAND_SRC; |
| int i; |
| |
| switch (dmae->opcode & DMAE_COMMAND_DST) { |
| case DMAE_CMD_DST_PCI: |
| if (src_type == DMAE_CMD_SRC_PCI) |
| DP(msglvl, "DMAE: opcode 0x%08x\n" |
| "src [%x:%08x], len [%d*4], dst [%x:%08x]\n" |
| "comp_addr [%x:%08x], comp_val 0x%08x\n", |
| dmae->opcode, dmae->src_addr_hi, dmae->src_addr_lo, |
| dmae->len, dmae->dst_addr_hi, dmae->dst_addr_lo, |
| dmae->comp_addr_hi, dmae->comp_addr_lo, |
| dmae->comp_val); |
| else |
| DP(msglvl, "DMAE: opcode 0x%08x\n" |
| "src [%08x], len [%d*4], dst [%x:%08x]\n" |
| "comp_addr [%x:%08x], comp_val 0x%08x\n", |
| dmae->opcode, dmae->src_addr_lo >> 2, |
| dmae->len, dmae->dst_addr_hi, dmae->dst_addr_lo, |
| dmae->comp_addr_hi, dmae->comp_addr_lo, |
| dmae->comp_val); |
| break; |
| case DMAE_CMD_DST_GRC: |
| if (src_type == DMAE_CMD_SRC_PCI) |
| DP(msglvl, "DMAE: opcode 0x%08x\n" |
| "src [%x:%08x], len [%d*4], dst_addr [%08x]\n" |
| "comp_addr [%x:%08x], comp_val 0x%08x\n", |
| dmae->opcode, dmae->src_addr_hi, dmae->src_addr_lo, |
| dmae->len, dmae->dst_addr_lo >> 2, |
| dmae->comp_addr_hi, dmae->comp_addr_lo, |
| dmae->comp_val); |
| else |
| DP(msglvl, "DMAE: opcode 0x%08x\n" |
| "src [%08x], len [%d*4], dst [%08x]\n" |
| "comp_addr [%x:%08x], comp_val 0x%08x\n", |
| dmae->opcode, dmae->src_addr_lo >> 2, |
| dmae->len, dmae->dst_addr_lo >> 2, |
| dmae->comp_addr_hi, dmae->comp_addr_lo, |
| dmae->comp_val); |
| break; |
| default: |
| if (src_type == DMAE_CMD_SRC_PCI) |
| DP(msglvl, "DMAE: opcode 0x%08x\n" |
| "src_addr [%x:%08x] len [%d * 4] dst_addr [none]\n" |
| "comp_addr [%x:%08x] comp_val 0x%08x\n", |
| dmae->opcode, dmae->src_addr_hi, dmae->src_addr_lo, |
| dmae->len, dmae->comp_addr_hi, dmae->comp_addr_lo, |
| dmae->comp_val); |
| else |
| DP(msglvl, "DMAE: opcode 0x%08x\n" |
| "src_addr [%08x] len [%d * 4] dst_addr [none]\n" |
| "comp_addr [%x:%08x] comp_val 0x%08x\n", |
| dmae->opcode, dmae->src_addr_lo >> 2, |
| dmae->len, dmae->comp_addr_hi, dmae->comp_addr_lo, |
| dmae->comp_val); |
| break; |
| } |
| |
| for (i = 0; i < (sizeof(struct dmae_command)/4); i++) |
| DP(msglvl, "DMAE RAW [%02d]: 0x%08x\n", |
| i, *(((u32 *)dmae) + i)); |
| } |
| |
| /* copy command into DMAE command memory and set DMAE command go */ |
| void bnx2x_post_dmae(struct bnx2x *bp, struct dmae_command *dmae, int idx) |
| { |
| u32 cmd_offset; |
| int i; |
| |
| cmd_offset = (DMAE_REG_CMD_MEM + sizeof(struct dmae_command) * idx); |
| for (i = 0; i < (sizeof(struct dmae_command)/4); i++) { |
| REG_WR(bp, cmd_offset + i*4, *(((u32 *)dmae) + i)); |
| } |
| REG_WR(bp, dmae_reg_go_c[idx], 1); |
| } |
| |
| u32 bnx2x_dmae_opcode_add_comp(u32 opcode, u8 comp_type) |
| { |
| return opcode | ((comp_type << DMAE_COMMAND_C_DST_SHIFT) | |
| DMAE_CMD_C_ENABLE); |
| } |
| |
| u32 bnx2x_dmae_opcode_clr_src_reset(u32 opcode) |
| { |
| return opcode & ~DMAE_CMD_SRC_RESET; |
| } |
| |
| u32 bnx2x_dmae_opcode(struct bnx2x *bp, u8 src_type, u8 dst_type, |
| bool with_comp, u8 comp_type) |
| { |
| u32 opcode = 0; |
| |
| opcode |= ((src_type << DMAE_COMMAND_SRC_SHIFT) | |
| (dst_type << DMAE_COMMAND_DST_SHIFT)); |
| |
| opcode |= (DMAE_CMD_SRC_RESET | DMAE_CMD_DST_RESET); |
| |
| opcode |= (BP_PORT(bp) ? DMAE_CMD_PORT_1 : DMAE_CMD_PORT_0); |
| opcode |= ((BP_VN(bp) << DMAE_CMD_E1HVN_SHIFT) | |
| (BP_VN(bp) << DMAE_COMMAND_DST_VN_SHIFT)); |
| opcode |= (DMAE_COM_SET_ERR << DMAE_COMMAND_ERR_POLICY_SHIFT); |
| |
| #ifdef __BIG_ENDIAN |
| opcode |= DMAE_CMD_ENDIANITY_B_DW_SWAP; |
| #else |
| opcode |= DMAE_CMD_ENDIANITY_DW_SWAP; |
| #endif |
| if (with_comp) |
| opcode = bnx2x_dmae_opcode_add_comp(opcode, comp_type); |
| return opcode; |
| } |
| |
| void bnx2x_prep_dmae_with_comp(struct bnx2x *bp, |
| struct dmae_command *dmae, |
| u8 src_type, u8 dst_type) |
| { |
| memset(dmae, 0, sizeof(struct dmae_command)); |
| |
| /* set the opcode */ |
| dmae->opcode = bnx2x_dmae_opcode(bp, src_type, dst_type, |
| true, DMAE_COMP_PCI); |
| |
| /* fill in the completion parameters */ |
| dmae->comp_addr_lo = U64_LO(bnx2x_sp_mapping(bp, wb_comp)); |
| dmae->comp_addr_hi = U64_HI(bnx2x_sp_mapping(bp, wb_comp)); |
| dmae->comp_val = DMAE_COMP_VAL; |
| } |
| |
| /* issue a dmae command over the init-channel and wait for completion */ |
| int bnx2x_issue_dmae_with_comp(struct bnx2x *bp, struct dmae_command *dmae, |
| u32 *comp) |
| { |
| int cnt = CHIP_REV_IS_SLOW(bp) ? (400000) : 4000; |
| int rc = 0; |
| |
| bnx2x_dp_dmae(bp, dmae, BNX2X_MSG_DMAE); |
| |
| /* Lock the dmae channel. Disable BHs to prevent a dead-lock |
| * as long as this code is called both from syscall context and |
| * from ndo_set_rx_mode() flow that may be called from BH. |
| */ |
| |
| spin_lock_bh(&bp->dmae_lock); |
| |
| /* reset completion */ |
| *comp = 0; |
| |
| /* post the command on the channel used for initializations */ |
| bnx2x_post_dmae(bp, dmae, INIT_DMAE_C(bp)); |
| |
| /* wait for completion */ |
| udelay(5); |
| while ((*comp & ~DMAE_PCI_ERR_FLAG) != DMAE_COMP_VAL) { |
| |
| if (!cnt || |
| (bp->recovery_state != BNX2X_RECOVERY_DONE && |
| bp->recovery_state != BNX2X_RECOVERY_NIC_LOADING)) { |
| BNX2X_ERR("DMAE timeout!\n"); |
| rc = DMAE_TIMEOUT; |
| goto unlock; |
| } |
| cnt--; |
| udelay(50); |
| } |
| if (*comp & DMAE_PCI_ERR_FLAG) { |
| BNX2X_ERR("DMAE PCI error!\n"); |
| rc = DMAE_PCI_ERROR; |
| } |
| |
| unlock: |
| |
| spin_unlock_bh(&bp->dmae_lock); |
| |
| return rc; |
| } |
| |
| void bnx2x_write_dmae(struct bnx2x *bp, dma_addr_t dma_addr, u32 dst_addr, |
| u32 len32) |
| { |
| int rc; |
| struct dmae_command dmae; |
| |
| if (!bp->dmae_ready) { |
| u32 *data = bnx2x_sp(bp, wb_data[0]); |
| |
| if (CHIP_IS_E1(bp)) |
| bnx2x_init_ind_wr(bp, dst_addr, data, len32); |
| else |
| bnx2x_init_str_wr(bp, dst_addr, data, len32); |
| return; |
| } |
| |
| /* set opcode and fixed command fields */ |
| bnx2x_prep_dmae_with_comp(bp, &dmae, DMAE_SRC_PCI, DMAE_DST_GRC); |
| |
| /* fill in addresses and len */ |
| dmae.src_addr_lo = U64_LO(dma_addr); |
| dmae.src_addr_hi = U64_HI(dma_addr); |
| dmae.dst_addr_lo = dst_addr >> 2; |
| dmae.dst_addr_hi = 0; |
| dmae.len = len32; |
| |
| /* issue the command and wait for completion */ |
| rc = bnx2x_issue_dmae_with_comp(bp, &dmae, bnx2x_sp(bp, wb_comp)); |
| if (rc) { |
| BNX2X_ERR("DMAE returned failure %d\n", rc); |
| #ifdef BNX2X_STOP_ON_ERROR |
| bnx2x_panic(); |
| #endif |
| } |
| } |
| |
| void bnx2x_read_dmae(struct bnx2x *bp, u32 src_addr, u32 len32) |
| { |
| int rc; |
| struct dmae_command dmae; |
| |
| if (!bp->dmae_ready) { |
| u32 *data = bnx2x_sp(bp, wb_data[0]); |
| int i; |
| |
| if (CHIP_IS_E1(bp)) |
| for (i = 0; i < len32; i++) |
| data[i] = bnx2x_reg_rd_ind(bp, src_addr + i*4); |
| else |
| for (i = 0; i < len32; i++) |
| data[i] = REG_RD(bp, src_addr + i*4); |
| |
| return; |
| } |
| |
| /* set opcode and fixed command fields */ |
| bnx2x_prep_dmae_with_comp(bp, &dmae, DMAE_SRC_GRC, DMAE_DST_PCI); |
| |
| /* fill in addresses and len */ |
| dmae.src_addr_lo = src_addr >> 2; |
| dmae.src_addr_hi = 0; |
| dmae.dst_addr_lo = U64_LO(bnx2x_sp_mapping(bp, wb_data)); |
| dmae.dst_addr_hi = U64_HI(bnx2x_sp_mapping(bp, wb_data)); |
| dmae.len = len32; |
| |
| /* issue the command and wait for completion */ |
| rc = bnx2x_issue_dmae_with_comp(bp, &dmae, bnx2x_sp(bp, wb_comp)); |
| if (rc) { |
| BNX2X_ERR("DMAE returned failure %d\n", rc); |
| #ifdef BNX2X_STOP_ON_ERROR |
| bnx2x_panic(); |
| #endif |
| } |
| } |
| |
| static void bnx2x_write_dmae_phys_len(struct bnx2x *bp, dma_addr_t phys_addr, |
| u32 addr, u32 len) |
| { |
| int dmae_wr_max = DMAE_LEN32_WR_MAX(bp); |
| int offset = 0; |
| |
| while (len > dmae_wr_max) { |
| bnx2x_write_dmae(bp, phys_addr + offset, |
| addr + offset, dmae_wr_max); |
| offset += dmae_wr_max * 4; |
| len -= dmae_wr_max; |
| } |
| |
| bnx2x_write_dmae(bp, phys_addr + offset, addr + offset, len); |
| } |
| |
| enum storms { |
| XSTORM, |
| TSTORM, |
| CSTORM, |
| USTORM, |
| MAX_STORMS |
| }; |
| |
| #define STORMS_NUM 4 |
| #define REGS_IN_ENTRY 4 |
| |
| static inline int bnx2x_get_assert_list_entry(struct bnx2x *bp, |
| enum storms storm, |
| int entry) |
| { |
| switch (storm) { |
| case XSTORM: |
| return XSTORM_ASSERT_LIST_OFFSET(entry); |
| case TSTORM: |
| return TSTORM_ASSERT_LIST_OFFSET(entry); |
| case CSTORM: |
| return CSTORM_ASSERT_LIST_OFFSET(entry); |
| case USTORM: |
| return USTORM_ASSERT_LIST_OFFSET(entry); |
| case MAX_STORMS: |
| default: |
| BNX2X_ERR("unknown storm\n"); |
| } |
| return -EINVAL; |
| } |
| |
| static int bnx2x_mc_assert(struct bnx2x *bp) |
| { |
| char last_idx; |
| int i, j, rc = 0; |
| enum storms storm; |
| u32 regs[REGS_IN_ENTRY]; |
| u32 bar_storm_intmem[STORMS_NUM] = { |
| BAR_XSTRORM_INTMEM, |
| BAR_TSTRORM_INTMEM, |
| BAR_CSTRORM_INTMEM, |
| BAR_USTRORM_INTMEM |
| }; |
| u32 storm_assert_list_index[STORMS_NUM] = { |
| XSTORM_ASSERT_LIST_INDEX_OFFSET, |
| TSTORM_ASSERT_LIST_INDEX_OFFSET, |
| CSTORM_ASSERT_LIST_INDEX_OFFSET, |
| USTORM_ASSERT_LIST_INDEX_OFFSET |
| }; |
| char *storms_string[STORMS_NUM] = { |
| "XSTORM", |
| "TSTORM", |
| "CSTORM", |
| "USTORM" |
| }; |
| |
| for (storm = XSTORM; storm < MAX_STORMS; storm++) { |
| last_idx = REG_RD8(bp, bar_storm_intmem[storm] + |
| storm_assert_list_index[storm]); |
| if (last_idx) |
| BNX2X_ERR("%s_ASSERT_LIST_INDEX 0x%x\n", |
| storms_string[storm], last_idx); |
| |
| /* print the asserts */ |
| for (i = 0; i < STROM_ASSERT_ARRAY_SIZE; i++) { |
| /* read a single assert entry */ |
| for (j = 0; j < REGS_IN_ENTRY; j++) |
| regs[j] = REG_RD(bp, bar_storm_intmem[storm] + |
| bnx2x_get_assert_list_entry(bp, |
| storm, |
| i) + |
| sizeof(u32) * j); |
| |
| /* log entry if it contains a valid assert */ |
| if (regs[0] != COMMON_ASM_INVALID_ASSERT_OPCODE) { |
| BNX2X_ERR("%s_ASSERT_INDEX 0x%x = 0x%08x 0x%08x 0x%08x 0x%08x\n", |
| storms_string[storm], i, regs[3], |
| regs[2], regs[1], regs[0]); |
| rc++; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| BNX2X_ERR("Chip Revision: %s, FW Version: %d_%d_%d\n", |
| CHIP_IS_E1(bp) ? "everest1" : |
| CHIP_IS_E1H(bp) ? "everest1h" : |
| CHIP_IS_E2(bp) ? "everest2" : "everest3", |
| bp->fw_major, bp->fw_minor, bp->fw_rev); |
| |
| return rc; |
| } |
| |
| #define MCPR_TRACE_BUFFER_SIZE (0x800) |
| #define SCRATCH_BUFFER_SIZE(bp) \ |
| (CHIP_IS_E1(bp) ? 0x10000 : (CHIP_IS_E1H(bp) ? 0x20000 : 0x28000)) |
| |
| void bnx2x_fw_dump_lvl(struct bnx2x *bp, const char *lvl) |
| { |
| u32 addr, val; |
| u32 mark, offset; |
| __be32 data[9]; |
| int word; |
| u32 trace_shmem_base; |
| if (BP_NOMCP(bp)) { |
| BNX2X_ERR("NO MCP - can not dump\n"); |
| return; |
| } |
| netdev_printk(lvl, bp->dev, "bc %d.%d.%d\n", |
| (bp->common.bc_ver & 0xff0000) >> 16, |
| (bp->common.bc_ver & 0xff00) >> 8, |
| (bp->common.bc_ver & 0xff)); |
| |
| if (pci_channel_offline(bp->pdev)) { |
| BNX2X_ERR("Cannot dump MCP info while in PCI error\n"); |
| return; |
| } |
| |
| val = REG_RD(bp, MCP_REG_MCPR_CPU_PROGRAM_COUNTER); |
| if (val == REG_RD(bp, MCP_REG_MCPR_CPU_PROGRAM_COUNTER)) |
| BNX2X_ERR("%s" "MCP PC at 0x%x\n", lvl, val); |
| |
| if (BP_PATH(bp) == 0) |
| trace_shmem_base = bp->common.shmem_base; |
| else |
| trace_shmem_base = SHMEM2_RD(bp, other_shmem_base_addr); |
| |
| /* sanity */ |
| if (trace_shmem_base < MCPR_SCRATCH_BASE(bp) + MCPR_TRACE_BUFFER_SIZE || |
| trace_shmem_base >= MCPR_SCRATCH_BASE(bp) + |
| SCRATCH_BUFFER_SIZE(bp)) { |
| BNX2X_ERR("Unable to dump trace buffer (mark %x)\n", |
| trace_shmem_base); |
| return; |
| } |
| |
| addr = trace_shmem_base - MCPR_TRACE_BUFFER_SIZE; |
| |
| /* validate TRCB signature */ |
| mark = REG_RD(bp, addr); |
| if (mark != MFW_TRACE_SIGNATURE) { |
| BNX2X_ERR("Trace buffer signature is missing."); |
| return ; |
| } |
| |
| /* read cyclic buffer pointer */ |
| addr += 4; |
| mark = REG_RD(bp, addr); |
| mark = MCPR_SCRATCH_BASE(bp) + ((mark + 0x3) & ~0x3) - 0x08000000; |
| if (mark >= trace_shmem_base || mark < addr + 4) { |
| BNX2X_ERR("Mark doesn't fall inside Trace Buffer\n"); |
| return; |
| } |
| printk("%s" "begin fw dump (mark 0x%x)\n", lvl, mark); |
| |
| printk("%s", lvl); |
| |
| /* dump buffer after the mark */ |
| for (offset = mark; offset < trace_shmem_base; offset += 0x8*4) { |
| for (word = 0; word < 8; word++) |
| data[word] = htonl(REG_RD(bp, offset + 4*word)); |
| data[8] = 0x0; |
| pr_cont("%s", (char *)data); |
| } |
| |
| /* dump buffer before the mark */ |
| for (offset = addr + 4; offset <= mark; offset += 0x8*4) { |
| for (word = 0; word < 8; word++) |
| data[word] = htonl(REG_RD(bp, offset + 4*word)); |
| data[8] = 0x0; |
| pr_cont("%s", (char *)data); |
| } |
| printk("%s" "end of fw dump\n", lvl); |
| } |
| |
| static void bnx2x_fw_dump(struct bnx2x *bp) |
| { |
| bnx2x_fw_dump_lvl(bp, KERN_ERR); |
| } |
| |
| static void bnx2x_hc_int_disable(struct bnx2x *bp) |
| { |
| int port = BP_PORT(bp); |
| u32 addr = port ? HC_REG_CONFIG_1 : HC_REG_CONFIG_0; |
| u32 val = REG_RD(bp, addr); |
| |
| /* in E1 we must use only PCI configuration space to disable |
| * MSI/MSIX capability |
| * It's forbidden to disable IGU_PF_CONF_MSI_MSIX_EN in HC block |
| */ |
| if (CHIP_IS_E1(bp)) { |
| /* Since IGU_PF_CONF_MSI_MSIX_EN still always on |
| * Use mask register to prevent from HC sending interrupts |
| * after we exit the function |
| */ |
| REG_WR(bp, HC_REG_INT_MASK + port*4, 0); |
| |
| val &= ~(HC_CONFIG_0_REG_SINGLE_ISR_EN_0 | |
| HC_CONFIG_0_REG_INT_LINE_EN_0 | |
| HC_CONFIG_0_REG_ATTN_BIT_EN_0); |
| } else |
| val &= ~(HC_CONFIG_0_REG_SINGLE_ISR_EN_0 | |
| HC_CONFIG_0_REG_MSI_MSIX_INT_EN_0 | |
| HC_CONFIG_0_REG_INT_LINE_EN_0 | |
| HC_CONFIG_0_REG_ATTN_BIT_EN_0); |
| |
| DP(NETIF_MSG_IFDOWN, |
| "write %x to HC %d (addr 0x%x)\n", |
| val, port, addr); |
| |
| REG_WR(bp, addr, val); |
| if (REG_RD(bp, addr) != val) |
| BNX2X_ERR("BUG! Proper val not read from IGU!\n"); |
| } |
| |
| static void bnx2x_igu_int_disable(struct bnx2x *bp) |
| { |
| u32 val = REG_RD(bp, IGU_REG_PF_CONFIGURATION); |
| |
| val &= ~(IGU_PF_CONF_MSI_MSIX_EN | |
| IGU_PF_CONF_INT_LINE_EN | |
| IGU_PF_CONF_ATTN_BIT_EN); |
| |
| DP(NETIF_MSG_IFDOWN, "write %x to IGU\n", val); |
| |
| REG_WR(bp, IGU_REG_PF_CONFIGURATION, val); |
| if (REG_RD(bp, IGU_REG_PF_CONFIGURATION) != val) |
| BNX2X_ERR("BUG! Proper val not read from IGU!\n"); |
| } |
| |
| static void bnx2x_int_disable(struct bnx2x *bp) |
| { |
| if (bp->common.int_block == INT_BLOCK_HC) |
| bnx2x_hc_int_disable(bp); |
| else |
| bnx2x_igu_int_disable(bp); |
| } |
| |
| void bnx2x_panic_dump(struct bnx2x *bp, bool disable_int) |
| { |
| int i; |
| u16 j; |
| struct hc_sp_status_block_data sp_sb_data; |
| int func = BP_FUNC(bp); |
| #ifdef BNX2X_STOP_ON_ERROR |
| u16 start = 0, end = 0; |
| u8 cos; |
| #endif |
| if (IS_PF(bp) && disable_int) |
| bnx2x_int_disable(bp); |
| |
| bp->stats_state = STATS_STATE_DISABLED; |
| bp->eth_stats.unrecoverable_error++; |
| DP(BNX2X_MSG_STATS, "stats_state - DISABLED\n"); |
| |
| BNX2X_ERR("begin crash dump -----------------\n"); |
| |
| /* Indices */ |
| /* Common */ |
| if (IS_PF(bp)) { |
| struct host_sp_status_block *def_sb = bp->def_status_blk; |
| int data_size, cstorm_offset; |
| |
| BNX2X_ERR("def_idx(0x%x) def_att_idx(0x%x) attn_state(0x%x) spq_prod_idx(0x%x) next_stats_cnt(0x%x)\n", |
| bp->def_idx, bp->def_att_idx, bp->attn_state, |
| bp->spq_prod_idx, bp->stats_counter); |
| BNX2X_ERR("DSB: attn bits(0x%x) ack(0x%x) id(0x%x) idx(0x%x)\n", |
| def_sb->atten_status_block.attn_bits, |
| def_sb->atten_status_block.attn_bits_ack, |
| def_sb->atten_status_block.status_block_id, |
| def_sb->atten_status_block.attn_bits_index); |
| BNX2X_ERR(" def ("); |
| for (i = 0; i < HC_SP_SB_MAX_INDICES; i++) |
| pr_cont("0x%x%s", |
| def_sb->sp_sb.index_values[i], |
| (i == HC_SP_SB_MAX_INDICES - 1) ? ") " : " "); |
| |
| data_size = sizeof(struct hc_sp_status_block_data) / |
| sizeof(u32); |
| cstorm_offset = CSTORM_SP_STATUS_BLOCK_DATA_OFFSET(func); |
| for (i = 0; i < data_size; i++) |
| *((u32 *)&sp_sb_data + i) = |
| REG_RD(bp, BAR_CSTRORM_INTMEM + cstorm_offset + |
| i * sizeof(u32)); |
| |
| pr_cont("igu_sb_id(0x%x) igu_seg_id(0x%x) pf_id(0x%x) vnic_id(0x%x) vf_id(0x%x) vf_valid (0x%x) state(0x%x)\n", |
| sp_sb_data.igu_sb_id, |
| sp_sb_data.igu_seg_id, |
| sp_sb_data.p_func.pf_id, |
| sp_sb_data.p_func.vnic_id, |
| sp_sb_data.p_func.vf_id, |
| sp_sb_data.p_func.vf_valid, |
| sp_sb_data.state); |
| } |
| |
| for_each_eth_queue(bp, i) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| int loop; |
| struct hc_status_block_data_e2 sb_data_e2; |
| struct hc_status_block_data_e1x sb_data_e1x; |
| struct hc_status_block_sm *hc_sm_p = |
| CHIP_IS_E1x(bp) ? |
| sb_data_e1x.common.state_machine : |
| sb_data_e2.common.state_machine; |
| struct hc_index_data *hc_index_p = |
| CHIP_IS_E1x(bp) ? |
| sb_data_e1x.index_data : |
| sb_data_e2.index_data; |
| u8 data_size, cos; |
| u32 *sb_data_p; |
| struct bnx2x_fp_txdata txdata; |
| |
| if (!bp->fp) |
| break; |
| |
| if (!fp->rx_cons_sb) |
| continue; |
| |
| /* Rx */ |
| BNX2X_ERR("fp%d: rx_bd_prod(0x%x) rx_bd_cons(0x%x) rx_comp_prod(0x%x) rx_comp_cons(0x%x) *rx_cons_sb(0x%x)\n", |
| i, fp->rx_bd_prod, fp->rx_bd_cons, |
| fp->rx_comp_prod, |
| fp->rx_comp_cons, le16_to_cpu(*fp->rx_cons_sb)); |
| BNX2X_ERR(" rx_sge_prod(0x%x) last_max_sge(0x%x) fp_hc_idx(0x%x)\n", |
| fp->rx_sge_prod, fp->last_max_sge, |
| le16_to_cpu(fp->fp_hc_idx)); |
| |
| /* Tx */ |
| for_each_cos_in_tx_queue(fp, cos) |
| { |
| if (!fp->txdata_ptr[cos]) |
| break; |
| |
| txdata = *fp->txdata_ptr[cos]; |
| |
| if (!txdata.tx_cons_sb) |
| continue; |
| |
| BNX2X_ERR("fp%d: tx_pkt_prod(0x%x) tx_pkt_cons(0x%x) tx_bd_prod(0x%x) tx_bd_cons(0x%x) *tx_cons_sb(0x%x)\n", |
| i, txdata.tx_pkt_prod, |
| txdata.tx_pkt_cons, txdata.tx_bd_prod, |
| txdata.tx_bd_cons, |
| le16_to_cpu(*txdata.tx_cons_sb)); |
| } |
| |
| loop = CHIP_IS_E1x(bp) ? |
| HC_SB_MAX_INDICES_E1X : HC_SB_MAX_INDICES_E2; |
| |
| /* host sb data */ |
| |
| if (IS_FCOE_FP(fp)) |
| continue; |
| |
| BNX2X_ERR(" run indexes ("); |
| for (j = 0; j < HC_SB_MAX_SM; j++) |
| pr_cont("0x%x%s", |
| fp->sb_running_index[j], |
| (j == HC_SB_MAX_SM - 1) ? ")" : " "); |
| |
| BNX2X_ERR(" indexes ("); |
| for (j = 0; j < loop; j++) |
| pr_cont("0x%x%s", |
| fp->sb_index_values[j], |
| (j == loop - 1) ? ")" : " "); |
| |
| /* VF cannot access FW refelection for status block */ |
| if (IS_VF(bp)) |
| continue; |
| |
| /* fw sb data */ |
| data_size = CHIP_IS_E1x(bp) ? |
| sizeof(struct hc_status_block_data_e1x) : |
| sizeof(struct hc_status_block_data_e2); |
| data_size /= sizeof(u32); |
| sb_data_p = CHIP_IS_E1x(bp) ? |
| (u32 *)&sb_data_e1x : |
| (u32 *)&sb_data_e2; |
| /* copy sb data in here */ |
| for (j = 0; j < data_size; j++) |
| *(sb_data_p + j) = REG_RD(bp, BAR_CSTRORM_INTMEM + |
| CSTORM_STATUS_BLOCK_DATA_OFFSET(fp->fw_sb_id) + |
| j * sizeof(u32)); |
| |
| if (!CHIP_IS_E1x(bp)) { |
| pr_cont("pf_id(0x%x) vf_id(0x%x) vf_valid(0x%x) vnic_id(0x%x) same_igu_sb_1b(0x%x) state(0x%x)\n", |
| sb_data_e2.common.p_func.pf_id, |
| sb_data_e2.common.p_func.vf_id, |
| sb_data_e2.common.p_func.vf_valid, |
| sb_data_e2.common.p_func.vnic_id, |
| sb_data_e2.common.same_igu_sb_1b, |
| sb_data_e2.common.state); |
| } else { |
| pr_cont("pf_id(0x%x) vf_id(0x%x) vf_valid(0x%x) vnic_id(0x%x) same_igu_sb_1b(0x%x) state(0x%x)\n", |
| sb_data_e1x.common.p_func.pf_id, |
| sb_data_e1x.common.p_func.vf_id, |
| sb_data_e1x.common.p_func.vf_valid, |
| sb_data_e1x.common.p_func.vnic_id, |
| sb_data_e1x.common.same_igu_sb_1b, |
| sb_data_e1x.common.state); |
| } |
| |
| /* SB_SMs data */ |
| for (j = 0; j < HC_SB_MAX_SM; j++) { |
| pr_cont("SM[%d] __flags (0x%x) igu_sb_id (0x%x) igu_seg_id(0x%x) time_to_expire (0x%x) timer_value(0x%x)\n", |
| j, hc_sm_p[j].__flags, |
| hc_sm_p[j].igu_sb_id, |
| hc_sm_p[j].igu_seg_id, |
| hc_sm_p[j].time_to_expire, |
| hc_sm_p[j].timer_value); |
| } |
| |
| /* Indices data */ |
| for (j = 0; j < loop; j++) { |
| pr_cont("INDEX[%d] flags (0x%x) timeout (0x%x)\n", j, |
| hc_index_p[j].flags, |
| hc_index_p[j].timeout); |
| } |
| } |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (IS_PF(bp)) { |
| /* event queue */ |
| BNX2X_ERR("eq cons %x prod %x\n", bp->eq_cons, bp->eq_prod); |
| for (i = 0; i < NUM_EQ_DESC; i++) { |
| u32 *data = (u32 *)&bp->eq_ring[i].message.data; |
| |
| BNX2X_ERR("event queue [%d]: header: opcode %d, error %d\n", |
| i, bp->eq_ring[i].message.opcode, |
| bp->eq_ring[i].message.error); |
| BNX2X_ERR("data: %x %x %x\n", |
| data[0], data[1], data[2]); |
| } |
| } |
| |
| /* Rings */ |
| /* Rx */ |
| for_each_valid_rx_queue(bp, i) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| |
| if (!bp->fp) |
| break; |
| |
| if (!fp->rx_cons_sb) |
| continue; |
| |
| start = RX_BD(le16_to_cpu(*fp->rx_cons_sb) - 10); |
| end = RX_BD(le16_to_cpu(*fp->rx_cons_sb) + 503); |
| for (j = start; j != end; j = RX_BD(j + 1)) { |
| u32 *rx_bd = (u32 *)&fp->rx_desc_ring[j]; |
| struct sw_rx_bd *sw_bd = &fp->rx_buf_ring[j]; |
| |
| BNX2X_ERR("fp%d: rx_bd[%x]=[%x:%x] sw_bd=[%p]\n", |
| i, j, rx_bd[1], rx_bd[0], sw_bd->data); |
| } |
| |
| start = RX_SGE(fp->rx_sge_prod); |
| end = RX_SGE(fp->last_max_sge); |
| for (j = start; j != end; j = RX_SGE(j + 1)) { |
| u32 *rx_sge = (u32 *)&fp->rx_sge_ring[j]; |
| struct sw_rx_page *sw_page = &fp->rx_page_ring[j]; |
| |
| BNX2X_ERR("fp%d: rx_sge[%x]=[%x:%x] sw_page=[%p]\n", |
| i, j, rx_sge[1], rx_sge[0], sw_page->page); |
| } |
| |
| start = RCQ_BD(fp->rx_comp_cons - 10); |
| end = RCQ_BD(fp->rx_comp_cons + 503); |
| for (j = start; j != end; j = RCQ_BD(j + 1)) { |
| u32 *cqe = (u32 *)&fp->rx_comp_ring[j]; |
| |
| BNX2X_ERR("fp%d: cqe[%x]=[%x:%x:%x:%x]\n", |
| i, j, cqe[0], cqe[1], cqe[2], cqe[3]); |
| } |
| } |
| |
| /* Tx */ |
| for_each_valid_tx_queue(bp, i) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| |
| if (!bp->fp) |
| break; |
| |
| for_each_cos_in_tx_queue(fp, cos) { |
| struct bnx2x_fp_txdata *txdata = fp->txdata_ptr[cos]; |
| |
| if (!fp->txdata_ptr[cos]) |
| break; |
| |
| if (!txdata->tx_cons_sb) |
| continue; |
| |
| start = TX_BD(le16_to_cpu(*txdata->tx_cons_sb) - 10); |
| end = TX_BD(le16_to_cpu(*txdata->tx_cons_sb) + 245); |
| for (j = start; j != end; j = TX_BD(j + 1)) { |
| struct sw_tx_bd *sw_bd = |
| &txdata->tx_buf_ring[j]; |
| |
| BNX2X_ERR("fp%d: txdata %d, packet[%x]=[%p,%x]\n", |
| i, cos, j, sw_bd->skb, |
| sw_bd->first_bd); |
| } |
| |
| start = TX_BD(txdata->tx_bd_cons - 10); |
| end = TX_BD(txdata->tx_bd_cons + 254); |
| for (j = start; j != end; j = TX_BD(j + 1)) { |
| u32 *tx_bd = (u32 *)&txdata->tx_desc_ring[j]; |
| |
| BNX2X_ERR("fp%d: txdata %d, tx_bd[%x]=[%x:%x:%x:%x]\n", |
| i, cos, j, tx_bd[0], tx_bd[1], |
| tx_bd[2], tx_bd[3]); |
| } |
| } |
| } |
| #endif |
| if (IS_PF(bp)) { |
| int tmp_msg_en = bp->msg_enable; |
| |
| bnx2x_fw_dump(bp); |
| bp->msg_enable |= NETIF_MSG_HW; |
| BNX2X_ERR("Idle check (1st round) ----------\n"); |
| bnx2x_idle_chk(bp); |
| BNX2X_ERR("Idle check (2nd round) ----------\n"); |
| bnx2x_idle_chk(bp); |
| bp->msg_enable = tmp_msg_en; |
| bnx2x_mc_assert(bp); |
| } |
| |
| BNX2X_ERR("end crash dump -----------------\n"); |
| } |
| |
| /* |
| * FLR Support for E2 |
| * |
| * bnx2x_pf_flr_clnup() is called during nic_load in the per function HW |
| * initialization. |
| */ |
| #define FLR_WAIT_USEC 10000 /* 10 milliseconds */ |
| #define FLR_WAIT_INTERVAL 50 /* usec */ |
| #define FLR_POLL_CNT (FLR_WAIT_USEC/FLR_WAIT_INTERVAL) /* 200 */ |
| |
| struct pbf_pN_buf_regs { |
| int pN; |
| u32 init_crd; |
| u32 crd; |
| u32 crd_freed; |
| }; |
| |
| struct pbf_pN_cmd_regs { |
| int pN; |
| u32 lines_occup; |
| u32 lines_freed; |
| }; |
| |
| static void bnx2x_pbf_pN_buf_flushed(struct bnx2x *bp, |
| struct pbf_pN_buf_regs *regs, |
| u32 poll_count) |
| { |
| u32 init_crd, crd, crd_start, crd_freed, crd_freed_start; |
| u32 cur_cnt = poll_count; |
| |
| crd_freed = crd_freed_start = REG_RD(bp, regs->crd_freed); |
| crd = crd_start = REG_RD(bp, regs->crd); |
| init_crd = REG_RD(bp, regs->init_crd); |
| |
| DP(BNX2X_MSG_SP, "INIT CREDIT[%d] : %x\n", regs->pN, init_crd); |
| DP(BNX2X_MSG_SP, "CREDIT[%d] : s:%x\n", regs->pN, crd); |
| DP(BNX2X_MSG_SP, "CREDIT_FREED[%d]: s:%x\n", regs->pN, crd_freed); |
| |
| while ((crd != init_crd) && ((u32)SUB_S32(crd_freed, crd_freed_start) < |
| (init_crd - crd_start))) { |
| if (cur_cnt--) { |
| udelay(FLR_WAIT_INTERVAL); |
| crd = REG_RD(bp, regs->crd); |
| crd_freed = REG_RD(bp, regs->crd_freed); |
| } else { |
| DP(BNX2X_MSG_SP, "PBF tx buffer[%d] timed out\n", |
| regs->pN); |
| DP(BNX2X_MSG_SP, "CREDIT[%d] : c:%x\n", |
| regs->pN, crd); |
| DP(BNX2X_MSG_SP, "CREDIT_FREED[%d]: c:%x\n", |
| regs->pN, crd_freed); |
| break; |
| } |
| } |
| DP(BNX2X_MSG_SP, "Waited %d*%d usec for PBF tx buffer[%d]\n", |
| poll_count-cur_cnt, FLR_WAIT_INTERVAL, regs->pN); |
| } |
| |
| static void bnx2x_pbf_pN_cmd_flushed(struct bnx2x *bp, |
| struct pbf_pN_cmd_regs *regs, |
| u32 poll_count) |
| { |
| u32 occup, to_free, freed, freed_start; |
| u32 cur_cnt = poll_count; |
| |
| occup = to_free = REG_RD(bp, regs->lines_occup); |
| freed = freed_start = REG_RD(bp, regs->lines_freed); |
| |
| DP(BNX2X_MSG_SP, "OCCUPANCY[%d] : s:%x\n", regs->pN, occup); |
| DP(BNX2X_MSG_SP, "LINES_FREED[%d] : s:%x\n", regs->pN, freed); |
| |
| while (occup && ((u32)SUB_S32(freed, freed_start) < to_free)) { |
| if (cur_cnt--) { |
| udelay(FLR_WAIT_INTERVAL); |
| occup = REG_RD(bp, regs->lines_occup); |
| freed = REG_RD(bp, regs->lines_freed); |
| } else { |
| DP(BNX2X_MSG_SP, "PBF cmd queue[%d] timed out\n", |
| regs->pN); |
| DP(BNX2X_MSG_SP, "OCCUPANCY[%d] : s:%x\n", |
| regs->pN, occup); |
| DP(BNX2X_MSG_SP, "LINES_FREED[%d] : s:%x\n", |
| regs->pN, freed); |
| break; |
| } |
| } |
| DP(BNX2X_MSG_SP, "Waited %d*%d usec for PBF cmd queue[%d]\n", |
| poll_count-cur_cnt, FLR_WAIT_INTERVAL, regs->pN); |
| } |
| |
| static u32 bnx2x_flr_clnup_reg_poll(struct bnx2x *bp, u32 reg, |
| u32 expected, u32 poll_count) |
| { |
| u32 cur_cnt = poll_count; |
| u32 val; |
| |
| while ((val = REG_RD(bp, reg)) != expected && cur_cnt--) |
| udelay(FLR_WAIT_INTERVAL); |
| |
| return val; |
| } |
| |
| int bnx2x_flr_clnup_poll_hw_counter(struct bnx2x *bp, u32 reg, |
| char *msg, u32 poll_cnt) |
| { |
| u32 val = bnx2x_flr_clnup_reg_poll(bp, reg, 0, poll_cnt); |
| if (val != 0) { |
| BNX2X_ERR("%s usage count=%d\n", msg, val); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Common routines with VF FLR cleanup */ |
| u32 bnx2x_flr_clnup_poll_count(struct bnx2x *bp) |
| { |
| /* adjust polling timeout */ |
| if (CHIP_REV_IS_EMUL(bp)) |
| return FLR_POLL_CNT * 2000; |
| |
| if (CHIP_REV_IS_FPGA(bp)) |
| return FLR_POLL_CNT * 120; |
| |
| return FLR_POLL_CNT; |
| } |
| |
| void bnx2x_tx_hw_flushed(struct bnx2x *bp, u32 poll_count) |
| { |
| struct pbf_pN_cmd_regs cmd_regs[] = { |
| {0, (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_TQ_OCCUPANCY_Q0 : |
| PBF_REG_P0_TQ_OCCUPANCY, |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_TQ_LINES_FREED_CNT_Q0 : |
| PBF_REG_P0_TQ_LINES_FREED_CNT}, |
| {1, (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_TQ_OCCUPANCY_Q1 : |
| PBF_REG_P1_TQ_OCCUPANCY, |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_TQ_LINES_FREED_CNT_Q1 : |
| PBF_REG_P1_TQ_LINES_FREED_CNT}, |
| {4, (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_TQ_OCCUPANCY_LB_Q : |
| PBF_REG_P4_TQ_OCCUPANCY, |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_TQ_LINES_FREED_CNT_LB_Q : |
| PBF_REG_P4_TQ_LINES_FREED_CNT} |
| }; |
| |
| struct pbf_pN_buf_regs buf_regs[] = { |
| {0, (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_INIT_CRD_Q0 : |
| PBF_REG_P0_INIT_CRD , |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_CREDIT_Q0 : |
| PBF_REG_P0_CREDIT, |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_INTERNAL_CRD_FREED_CNT_Q0 : |
| PBF_REG_P0_INTERNAL_CRD_FREED_CNT}, |
| {1, (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_INIT_CRD_Q1 : |
| PBF_REG_P1_INIT_CRD, |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_CREDIT_Q1 : |
| PBF_REG_P1_CREDIT, |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_INTERNAL_CRD_FREED_CNT_Q1 : |
| PBF_REG_P1_INTERNAL_CRD_FREED_CNT}, |
| {4, (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_INIT_CRD_LB_Q : |
| PBF_REG_P4_INIT_CRD, |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_CREDIT_LB_Q : |
| PBF_REG_P4_CREDIT, |
| (CHIP_IS_E3B0(bp)) ? |
| PBF_REG_INTERNAL_CRD_FREED_CNT_LB_Q : |
| PBF_REG_P4_INTERNAL_CRD_FREED_CNT}, |
| }; |
| |
| int i; |
| |
| /* Verify the command queues are flushed P0, P1, P4 */ |
| for (i = 0; i < ARRAY_SIZE(cmd_regs); i++) |
| bnx2x_pbf_pN_cmd_flushed(bp, &cmd_regs[i], poll_count); |
| |
| /* Verify the transmission buffers are flushed P0, P1, P4 */ |
| for (i = 0; i < ARRAY_SIZE(buf_regs); i++) |
| bnx2x_pbf_pN_buf_flushed(bp, &buf_regs[i], poll_count); |
| } |
| |
| #define OP_GEN_PARAM(param) \ |
| (((param) << SDM_OP_GEN_COMP_PARAM_SHIFT) & SDM_OP_GEN_COMP_PARAM) |
| |
| #define OP_GEN_TYPE(type) \ |
| (((type) << SDM_OP_GEN_COMP_TYPE_SHIFT) & SDM_OP_GEN_COMP_TYPE) |
| |
| #define OP_GEN_AGG_VECT(index) \ |
| (((index) << SDM_OP_GEN_AGG_VECT_IDX_SHIFT) & SDM_OP_GEN_AGG_VECT_IDX) |
| |
| int bnx2x_send_final_clnup(struct bnx2x *bp, u8 clnup_func, u32 poll_cnt) |
| { |
| u32 op_gen_command = 0; |
| u32 comp_addr = BAR_CSTRORM_INTMEM + |
| CSTORM_FINAL_CLEANUP_COMPLETE_OFFSET(clnup_func); |
| |
| if (REG_RD(bp, comp_addr)) { |
| BNX2X_ERR("Cleanup complete was not 0 before sending\n"); |
| return 1; |
| } |
| |
| op_gen_command |= OP_GEN_PARAM(XSTORM_AGG_INT_FINAL_CLEANUP_INDEX); |
| op_gen_command |= OP_GEN_TYPE(XSTORM_AGG_INT_FINAL_CLEANUP_COMP_TYPE); |
| op_gen_command |= OP_GEN_AGG_VECT(clnup_func); |
| op_gen_command |= 1 << SDM_OP_GEN_AGG_VECT_IDX_VALID_SHIFT; |
| |
| DP(BNX2X_MSG_SP, "sending FW Final cleanup\n"); |
| REG_WR(bp, XSDM_REG_OPERATION_GEN, op_gen_command); |
| |
| if (bnx2x_flr_clnup_reg_poll(bp, comp_addr, 1, poll_cnt) != 1) { |
| BNX2X_ERR("FW final cleanup did not succeed\n"); |
| DP(BNX2X_MSG_SP, "At timeout completion address contained %x\n", |
| (REG_RD(bp, comp_addr))); |
| bnx2x_panic(); |
| return 1; |
| } |
| /* Zero completion for next FLR */ |
| REG_WR(bp, comp_addr, 0); |
| |
| return 0; |
| } |
| |
| u8 bnx2x_is_pcie_pending(struct pci_dev *dev) |
| { |
| u16 status; |
| |
| pcie_capability_read_word(dev, PCI_EXP_DEVSTA, &status); |
| return status & PCI_EXP_DEVSTA_TRPND; |
| } |
| |
| /* PF FLR specific routines |
| */ |
| static int bnx2x_poll_hw_usage_counters(struct bnx2x *bp, u32 poll_cnt) |
| { |
| /* wait for CFC PF usage-counter to zero (includes all the VFs) */ |
| if (bnx2x_flr_clnup_poll_hw_counter(bp, |
| CFC_REG_NUM_LCIDS_INSIDE_PF, |
| "CFC PF usage counter timed out", |
| poll_cnt)) |
| return 1; |
| |
| /* Wait for DQ PF usage-counter to zero (until DQ cleanup) */ |
| if (bnx2x_flr_clnup_poll_hw_counter(bp, |
| DORQ_REG_PF_USAGE_CNT, |
| "DQ PF usage counter timed out", |
| poll_cnt)) |
| return 1; |
| |
| /* Wait for QM PF usage-counter to zero (until DQ cleanup) */ |
| if (bnx2x_flr_clnup_poll_hw_counter(bp, |
| QM_REG_PF_USG_CNT_0 + 4*BP_FUNC(bp), |
| "QM PF usage counter timed out", |
| poll_cnt)) |
| return 1; |
| |
| /* Wait for Timer PF usage-counters to zero (until DQ cleanup) */ |
| if (bnx2x_flr_clnup_poll_hw_counter(bp, |
| TM_REG_LIN0_VNIC_UC + 4*BP_PORT(bp), |
| "Timers VNIC usage counter timed out", |
| poll_cnt)) |
| return 1; |
| if (bnx2x_flr_clnup_poll_hw_counter(bp, |
| TM_REG_LIN0_NUM_SCANS + 4*BP_PORT(bp), |
| "Timers NUM_SCANS usage counter timed out", |
| poll_cnt)) |
| return 1; |
| |
| /* Wait DMAE PF usage counter to zero */ |
| if (bnx2x_flr_clnup_poll_hw_counter(bp, |
| dmae_reg_go_c[INIT_DMAE_C(bp)], |
| "DMAE command register timed out", |
| poll_cnt)) |
| return 1; |
| |
| return 0; |
| } |
| |
| static void bnx2x_hw_enable_status(struct bnx2x *bp) |
| { |
| u32 val; |
| |
| val = REG_RD(bp, CFC_REG_WEAK_ENABLE_PF); |
| DP(BNX2X_MSG_SP, "CFC_REG_WEAK_ENABLE_PF is 0x%x\n", val); |
| |
| val = REG_RD(bp, PBF_REG_DISABLE_PF); |
| DP(BNX2X_MSG_SP, "PBF_REG_DISABLE_PF is 0x%x\n", val); |
| |
| val = REG_RD(bp, IGU_REG_PCI_PF_MSI_EN); |
| DP(BNX2X_MSG_SP, "IGU_REG_PCI_PF_MSI_EN is 0x%x\n", val); |
| |
| val = REG_RD(bp, IGU_REG_PCI_PF_MSIX_EN); |
| DP(BNX2X_MSG_SP, "IGU_REG_PCI_PF_MSIX_EN is 0x%x\n", val); |
| |
| val = REG_RD(bp, IGU_REG_PCI_PF_MSIX_FUNC_MASK); |
| DP(BNX2X_MSG_SP, "IGU_REG_PCI_PF_MSIX_FUNC_MASK is 0x%x\n", val); |
| |
| val = REG_RD(bp, PGLUE_B_REG_SHADOW_BME_PF_7_0_CLR); |
| DP(BNX2X_MSG_SP, "PGLUE_B_REG_SHADOW_BME_PF_7_0_CLR is 0x%x\n", val); |
| |
| val = REG_RD(bp, PGLUE_B_REG_FLR_REQUEST_PF_7_0_CLR); |
| DP(BNX2X_MSG_SP, "PGLUE_B_REG_FLR_REQUEST_PF_7_0_CLR is 0x%x\n", val); |
| |
| val = REG_RD(bp, PGLUE_B_REG_INTERNAL_PFID_ENABLE_MASTER); |
| DP(BNX2X_MSG_SP, "PGLUE_B_REG_INTERNAL_PFID_ENABLE_MASTER is 0x%x\n", |
| val); |
| } |
| |
| static int bnx2x_pf_flr_clnup(struct bnx2x *bp) |
| { |
| u32 poll_cnt = bnx2x_flr_clnup_poll_count(bp); |
| |
| DP(BNX2X_MSG_SP, "Cleanup after FLR PF[%d]\n", BP_ABS_FUNC(bp)); |
| |
| /* Re-enable PF target read access */ |
| REG_WR(bp, PGLUE_B_REG_INTERNAL_PFID_ENABLE_TARGET_READ, 1); |
| |
| /* Poll HW usage counters */ |
| DP(BNX2X_MSG_SP, "Polling usage counters\n"); |
| if (bnx2x_poll_hw_usage_counters(bp, poll_cnt)) |
| return -EBUSY; |
| |
| /* Zero the igu 'trailing edge' and 'leading edge' */ |
| |
| /* Send the FW cleanup command */ |
| if (bnx2x_send_final_clnup(bp, (u8)BP_FUNC(bp), poll_cnt)) |
| return -EBUSY; |
| |
| /* ATC cleanup */ |
| |
| /* Verify TX hw is flushed */ |
| bnx2x_tx_hw_flushed(bp, poll_cnt); |
| |
| /* Wait 100ms (not adjusted according to platform) */ |
| msleep(100); |
| |
| /* Verify no pending pci transactions */ |
| if (bnx2x_is_pcie_pending(bp->pdev)) |
| BNX2X_ERR("PCIE Transactions still pending\n"); |
| |
| /* Debug */ |
| bnx2x_hw_enable_status(bp); |
| |
| /* |
| * Master enable - Due to WB DMAE writes performed before this |
| * register is re-initialized as part of the regular function init |
| */ |
| REG_WR(bp, PGLUE_B_REG_INTERNAL_PFID_ENABLE_MASTER, 1); |
| |
| return 0; |
| } |
| |
| static void bnx2x_hc_int_enable(struct bnx2x *bp) |
| { |
| int port = BP_PORT(bp); |
| u32 addr = port ? HC_REG_CONFIG_1 : HC_REG_CONFIG_0; |
| u32 val = REG_RD(bp, addr); |
| bool msix = (bp->flags & USING_MSIX_FLAG) ? true : false; |
| bool single_msix = (bp->flags & USING_SINGLE_MSIX_FLAG) ? true : false; |
| bool msi = (bp->flags & USING_MSI_FLAG) ? true : false; |
| |
| if (msix) { |
| val &= ~(HC_CONFIG_0_REG_SINGLE_ISR_EN_0 | |
| HC_CONFIG_0_REG_INT_LINE_EN_0); |
| val |= (HC_CONFIG_0_REG_MSI_MSIX_INT_EN_0 | |
| HC_CONFIG_0_REG_ATTN_BIT_EN_0); |
| if (single_msix) |
| val |= HC_CONFIG_0_REG_SINGLE_ISR_EN_0; |
| } else if (msi) { |
| val &= ~HC_CONFIG_0_REG_INT_LINE_EN_0; |
| val |= (HC_CONFIG_0_REG_SINGLE_ISR_EN_0 | |
| HC_CONFIG_0_REG_MSI_MSIX_INT_EN_0 | |
| HC_CONFIG_0_REG_ATTN_BIT_EN_0); |
| } else { |
| val |= (HC_CONFIG_0_REG_SINGLE_ISR_EN_0 | |
| HC_CONFIG_0_REG_MSI_MSIX_INT_EN_0 | |
| HC_CONFIG_0_REG_INT_LINE_EN_0 | |
| HC_CONFIG_0_REG_ATTN_BIT_EN_0); |
| |
| if (!CHIP_IS_E1(bp)) { |
| DP(NETIF_MSG_IFUP, |
| "write %x to HC %d (addr 0x%x)\n", val, port, addr); |
| |
| REG_WR(bp, addr, val); |
| |
| val &= ~HC_CONFIG_0_REG_MSI_MSIX_INT_EN_0; |
| } |
| } |
| |
| if (CHIP_IS_E1(bp)) |
| REG_WR(bp, HC_REG_INT_MASK + port*4, 0x1FFFF); |
| |
| DP(NETIF_MSG_IFUP, |
| "write %x to HC %d (addr 0x%x) mode %s\n", val, port, addr, |
| (msix ? "MSI-X" : (msi ? "MSI" : "INTx"))); |
| |
| REG_WR(bp, addr, val); |
| /* |
| * Ensure that HC_CONFIG is written before leading/trailing edge config |
| */ |
| barrier(); |
| |
| if (!CHIP_IS_E1(bp)) { |
| /* init leading/trailing edge */ |
| if (IS_MF(bp)) { |
| val = (0xee0f | (1 << (BP_VN(bp) + 4))); |
| if (bp->port.pmf) |
| /* enable nig and gpio3 attention */ |
| val |= 0x1100; |
| } else |
| val = 0xffff; |
| |
| REG_WR(bp, HC_REG_TRAILING_EDGE_0 + port*8, val); |
| REG_WR(bp, HC_REG_LEADING_EDGE_0 + port*8, val); |
| } |
| } |
| |
| static void bnx2x_igu_int_enable(struct bnx2x *bp) |
| { |
| u32 val; |
| bool msix = (bp->flags & USING_MSIX_FLAG) ? true : false; |
| bool single_msix = (bp->flags & USING_SINGLE_MSIX_FLAG) ? true : false; |
| bool msi = (bp->flags & USING_MSI_FLAG) ? true : false; |
| |
| val = REG_RD(bp, IGU_REG_PF_CONFIGURATION); |
| |
| if (msix) { |
| val &= ~(IGU_PF_CONF_INT_LINE_EN | |
| IGU_PF_CONF_SINGLE_ISR_EN); |
| val |= (IGU_PF_CONF_MSI_MSIX_EN | |
| IGU_PF_CONF_ATTN_BIT_EN); |
| |
| if (single_msix) |
| val |= IGU_PF_CONF_SINGLE_ISR_EN; |
| } else if (msi) { |
| val &= ~IGU_PF_CONF_INT_LINE_EN; |
| val |= (IGU_PF_CONF_MSI_MSIX_EN | |
| IGU_PF_CONF_ATTN_BIT_EN | |
| IGU_PF_CONF_SINGLE_ISR_EN); |
| } else { |
| val &= ~IGU_PF_CONF_MSI_MSIX_EN; |
| val |= (IGU_PF_CONF_INT_LINE_EN | |
| IGU_PF_CONF_ATTN_BIT_EN | |
| IGU_PF_CONF_SINGLE_ISR_EN); |
| } |
| |
| /* Clean previous status - need to configure igu prior to ack*/ |
| if ((!msix) || single_msix) { |
| REG_WR(bp, IGU_REG_PF_CONFIGURATION, val); |
| bnx2x_ack_int(bp); |
| } |
| |
| val |= IGU_PF_CONF_FUNC_EN; |
| |
| DP(NETIF_MSG_IFUP, "write 0x%x to IGU mode %s\n", |
| val, (msix ? "MSI-X" : (msi ? "MSI" : "INTx"))); |
| |
| REG_WR(bp, IGU_REG_PF_CONFIGURATION, val); |
| |
| if (val & IGU_PF_CONF_INT_LINE_EN) |
| pci_intx(bp->pdev, true); |
| |
| barrier(); |
| |
| /* init leading/trailing edge */ |
| if (IS_MF(bp)) { |
| val = (0xee0f | (1 << (BP_VN(bp) + 4))); |
| if (bp->port.pmf) |
| /* enable nig and gpio3 attention */ |
| val |= 0x1100; |
| } else |
| val = 0xffff; |
| |
| REG_WR(bp, IGU_REG_TRAILING_EDGE_LATCH, val); |
| REG_WR(bp, IGU_REG_LEADING_EDGE_LATCH, val); |
| } |
| |
| void bnx2x_int_enable(struct bnx2x *bp) |
| { |
| if (bp->common.int_block == INT_BLOCK_HC) |
| bnx2x_hc_int_enable(bp); |
| else |
| bnx2x_igu_int_enable(bp); |
| } |
| |
| void bnx2x_int_disable_sync(struct bnx2x *bp, int disable_hw) |
| { |
| int msix = (bp->flags & USING_MSIX_FLAG) ? 1 : 0; |
| int i, offset; |
| |
| if (disable_hw) |
| /* prevent the HW from sending interrupts */ |
| bnx2x_int_disable(bp); |
| |
| /* make sure all ISRs are done */ |
| if (msix) { |
| synchronize_irq(bp->msix_table[0].vector); |
| offset = 1; |
| if (CNIC_SUPPORT(bp)) |
| offset++; |
| for_each_eth_queue(bp, i) |
| synchronize_irq(bp->msix_table[offset++].vector); |
| } else |
| synchronize_irq(bp->pdev->irq); |
| |
| /* make sure sp_task is not running */ |
| cancel_delayed_work(&bp->sp_task); |
| cancel_delayed_work(&bp->period_task); |
| flush_workqueue(bnx2x_wq); |
| } |
| |
| /* fast path */ |
| |
| /* |
| * General service functions |
| */ |
| |
| /* Return true if succeeded to acquire the lock */ |
| static bool bnx2x_trylock_hw_lock(struct bnx2x *bp, u32 resource) |
| { |
| u32 lock_status; |
| u32 resource_bit = (1 << resource); |
| int func = BP_FUNC(bp); |
| u32 hw_lock_control_reg; |
| |
| DP(NETIF_MSG_HW | NETIF_MSG_IFUP, |
| "Trying to take a lock on resource %d\n", resource); |
| |
| /* Validating that the resource is within range */ |
| if (resource > HW_LOCK_MAX_RESOURCE_VALUE) { |
| DP(NETIF_MSG_HW | NETIF_MSG_IFUP, |
| "resource(0x%x) > HW_LOCK_MAX_RESOURCE_VALUE(0x%x)\n", |
| resource, HW_LOCK_MAX_RESOURCE_VALUE); |
| return false; |
| } |
| |
| if (func <= 5) |
| hw_lock_control_reg = (MISC_REG_DRIVER_CONTROL_1 + func*8); |
| else |
| hw_lock_control_reg = |
| (MISC_REG_DRIVER_CONTROL_7 + (func - 6)*8); |
| |
| /* Try to acquire the lock */ |
| REG_WR(bp, hw_lock_control_reg + 4, resource_bit); |
| lock_status = REG_RD(bp, hw_lock_control_reg); |
| if (lock_status & resource_bit) |
| return true; |
| |
| DP(NETIF_MSG_HW | NETIF_MSG_IFUP, |
| "Failed to get a lock on resource %d\n", resource); |
| return false; |
| } |
| |
| /** |
| * bnx2x_get_leader_lock_resource - get the recovery leader resource id |
| * |
| * @bp: driver handle |
| * |
| * Returns the recovery leader resource id according to the engine this function |
| * belongs to. Currently only only 2 engines is supported. |
| */ |
| static int bnx2x_get_leader_lock_resource(struct bnx2x *bp) |
| { |
| if (BP_PATH(bp)) |
| return HW_LOCK_RESOURCE_RECOVERY_LEADER_1; |
| else |
| return HW_LOCK_RESOURCE_RECOVERY_LEADER_0; |
| } |
| |
| /** |
| * bnx2x_trylock_leader_lock- try to acquire a leader lock. |
| * |
| * @bp: driver handle |
| * |
| * Tries to acquire a leader lock for current engine. |
| */ |
| static bool bnx2x_trylock_leader_lock(struct bnx2x *bp) |
| { |
| return bnx2x_trylock_hw_lock(bp, bnx2x_get_leader_lock_resource(bp)); |
| } |
| |
| static void bnx2x_cnic_cfc_comp(struct bnx2x *bp, int cid, u8 err); |
| |
| /* schedule the sp task and mark that interrupt occurred (runs from ISR) */ |
| static int bnx2x_schedule_sp_task(struct bnx2x *bp) |
| { |
| /* Set the interrupt occurred bit for the sp-task to recognize it |
| * must ack the interrupt and transition according to the IGU |
| * state machine. |
| */ |
| atomic_set(&bp->interrupt_occurred, 1); |
| |
| /* The sp_task must execute only after this bit |
| * is set, otherwise we will get out of sync and miss all |
| * further interrupts. Hence, the barrier. |
| */ |
| smp_wmb(); |
| |
| /* schedule sp_task to workqueue */ |
| return queue_delayed_work(bnx2x_wq, &bp->sp_task, 0); |
| } |
| |
| void bnx2x_sp_event(struct bnx2x_fastpath *fp, union eth_rx_cqe *rr_cqe) |
| { |
| struct bnx2x *bp = fp->bp; |
| int cid = SW_CID(rr_cqe->ramrod_cqe.conn_and_cmd_data); |
| int command = CQE_CMD(rr_cqe->ramrod_cqe.conn_and_cmd_data); |
| enum bnx2x_queue_cmd drv_cmd = BNX2X_Q_CMD_MAX; |
| struct bnx2x_queue_sp_obj *q_obj = &bnx2x_sp_obj(bp, fp).q_obj; |
| |
| DP(BNX2X_MSG_SP, |
| "fp %d cid %d got ramrod #%d state is %x type is %d\n", |
| fp->index, cid, command, bp->state, |
| rr_cqe->ramrod_cqe.ramrod_type); |
| |
| /* If cid is within VF range, replace the slowpath object with the |
| * one corresponding to this VF |
| */ |
| if (cid >= BNX2X_FIRST_VF_CID && |
| cid < BNX2X_FIRST_VF_CID + BNX2X_VF_CIDS) |
| bnx2x_iov_set_queue_sp_obj(bp, cid, &q_obj); |
| |
| switch (command) { |
| case (RAMROD_CMD_ID_ETH_CLIENT_UPDATE): |
| DP(BNX2X_MSG_SP, "got UPDATE ramrod. CID %d\n", cid); |
| drv_cmd = BNX2X_Q_CMD_UPDATE; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_CLIENT_SETUP): |
| DP(BNX2X_MSG_SP, "got MULTI[%d] setup ramrod\n", cid); |
| drv_cmd = BNX2X_Q_CMD_SETUP; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_TX_QUEUE_SETUP): |
| DP(BNX2X_MSG_SP, "got MULTI[%d] tx-only setup ramrod\n", cid); |
| drv_cmd = BNX2X_Q_CMD_SETUP_TX_ONLY; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_HALT): |
| DP(BNX2X_MSG_SP, "got MULTI[%d] halt ramrod\n", cid); |
| drv_cmd = BNX2X_Q_CMD_HALT; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_TERMINATE): |
| DP(BNX2X_MSG_SP, "got MULTI[%d] terminate ramrod\n", cid); |
| drv_cmd = BNX2X_Q_CMD_TERMINATE; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_EMPTY): |
| DP(BNX2X_MSG_SP, "got MULTI[%d] empty ramrod\n", cid); |
| drv_cmd = BNX2X_Q_CMD_EMPTY; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_TPA_UPDATE): |
| DP(BNX2X_MSG_SP, "got tpa update ramrod CID=%d\n", cid); |
| drv_cmd = BNX2X_Q_CMD_UPDATE_TPA; |
| break; |
| |
| default: |
| BNX2X_ERR("unexpected MC reply (%d) on fp[%d]\n", |
| command, fp->index); |
| return; |
| } |
| |
| if ((drv_cmd != BNX2X_Q_CMD_MAX) && |
| q_obj->complete_cmd(bp, q_obj, drv_cmd)) |
| /* q_obj->complete_cmd() failure means that this was |
| * an unexpected completion. |
| * |
| * In this case we don't want to increase the bp->spq_left |
| * because apparently we haven't sent this command the first |
| * place. |
| */ |
| #ifdef BNX2X_STOP_ON_ERROR |
| bnx2x_panic(); |
| #else |
| return; |
| #endif |
| |
| smp_mb__before_atomic(); |
| atomic_inc(&bp->cq_spq_left); |
| /* push the change in bp->spq_left and towards the memory */ |
| smp_mb__after_atomic(); |
| |
| DP(BNX2X_MSG_SP, "bp->cq_spq_left %x\n", atomic_read(&bp->cq_spq_left)); |
| |
| if ((drv_cmd == BNX2X_Q_CMD_UPDATE) && (IS_FCOE_FP(fp)) && |
| (!!test_bit(BNX2X_AFEX_FCOE_Q_UPDATE_PENDING, &bp->sp_state))) { |
| /* if Q update ramrod is completed for last Q in AFEX vif set |
| * flow, then ACK MCP at the end |
| * |
| * mark pending ACK to MCP bit. |
| * prevent case that both bits are cleared. |
| * At the end of load/unload driver checks that |
| * sp_state is cleared, and this order prevents |
| * races |
| */ |
| smp_mb__before_atomic(); |
| set_bit(BNX2X_AFEX_PENDING_VIFSET_MCP_ACK, &bp->sp_state); |
| wmb(); |
| clear_bit(BNX2X_AFEX_FCOE_Q_UPDATE_PENDING, &bp->sp_state); |
| smp_mb__after_atomic(); |
| |
| /* schedule the sp task as mcp ack is required */ |
| bnx2x_schedule_sp_task(bp); |
| } |
| |
| return; |
| } |
| |
| irqreturn_t bnx2x_interrupt(int irq, void *dev_instance) |
| { |
| struct bnx2x *bp = netdev_priv(dev_instance); |
| u16 status = bnx2x_ack_int(bp); |
| u16 mask; |
| int i; |
| u8 cos; |
| |
| /* Return here if interrupt is shared and it's not for us */ |
| if (unlikely(status == 0)) { |
| DP(NETIF_MSG_INTR, "not our interrupt!\n"); |
| return IRQ_NONE; |
| } |
| DP(NETIF_MSG_INTR, "got an interrupt status 0x%x\n", status); |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (unlikely(bp->panic)) |
| return IRQ_HANDLED; |
| #endif |
| |
| for_each_eth_queue(bp, i) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| |
| mask = 0x2 << (fp->index + CNIC_SUPPORT(bp)); |
| if (status & mask) { |
| /* Handle Rx or Tx according to SB id */ |
| for_each_cos_in_tx_queue(fp, cos) |
| prefetch(fp->txdata_ptr[cos]->tx_cons_sb); |
| prefetch(&fp->sb_running_index[SM_RX_ID]); |
| napi_schedule_irqoff(&bnx2x_fp(bp, fp->index, napi)); |
| status &= ~mask; |
| } |
| } |
| |
| if (CNIC_SUPPORT(bp)) { |
| mask = 0x2; |
| if (status & (mask | 0x1)) { |
| struct cnic_ops *c_ops = NULL; |
| |
| rcu_read_lock(); |
| c_ops = rcu_dereference(bp->cnic_ops); |
| if (c_ops && (bp->cnic_eth_dev.drv_state & |
| CNIC_DRV_STATE_HANDLES_IRQ)) |
| c_ops->cnic_handler(bp->cnic_data, NULL); |
| rcu_read_unlock(); |
| |
| status &= ~mask; |
| } |
| } |
| |
| if (unlikely(status & 0x1)) { |
| |
| /* schedule sp task to perform default status block work, ack |
| * attentions and enable interrupts. |
| */ |
| bnx2x_schedule_sp_task(bp); |
| |
| status &= ~0x1; |
| if (!status) |
| return IRQ_HANDLED; |
| } |
| |
| if (unlikely(status)) |
| DP(NETIF_MSG_INTR, "got an unknown interrupt! (status 0x%x)\n", |
| status); |
| |
| return IRQ_HANDLED; |
| } |
| |
| /* Link */ |
| |
| /* |
| * General service functions |
| */ |
| |
| int bnx2x_acquire_hw_lock(struct bnx2x *bp, u32 resource) |
| { |
| u32 lock_status; |
| u32 resource_bit = (1 << resource); |
| int func = BP_FUNC(bp); |
| u32 hw_lock_control_reg; |
| int cnt; |
| |
| /* Validating that the resource is within range */ |
| if (resource > HW_LOCK_MAX_RESOURCE_VALUE) { |
| BNX2X_ERR("resource(0x%x) > HW_LOCK_MAX_RESOURCE_VALUE(0x%x)\n", |
| resource, HW_LOCK_MAX_RESOURCE_VALUE); |
| return -EINVAL; |
| } |
| |
| if (func <= 5) { |
| hw_lock_control_reg = (MISC_REG_DRIVER_CONTROL_1 + func*8); |
| } else { |
| hw_lock_control_reg = |
| (MISC_REG_DRIVER_CONTROL_7 + (func - 6)*8); |
| } |
| |
| /* Validating that the resource is not already taken */ |
| lock_status = REG_RD(bp, hw_lock_control_reg); |
| if (lock_status & resource_bit) { |
| BNX2X_ERR("lock_status 0x%x resource_bit 0x%x\n", |
| lock_status, resource_bit); |
| return -EEXIST; |
| } |
| |
| /* Try for 5 second every 5ms */ |
| for (cnt = 0; cnt < 1000; cnt++) { |
| /* Try to acquire the lock */ |
| REG_WR(bp, hw_lock_control_reg + 4, resource_bit); |
| lock_status = REG_RD(bp, hw_lock_control_reg); |
| if (lock_status & resource_bit) |
| return 0; |
| |
| usleep_range(5000, 10000); |
| } |
| BNX2X_ERR("Timeout\n"); |
| return -EAGAIN; |
| } |
| |
| int bnx2x_release_leader_lock(struct bnx2x *bp) |
| { |
| return bnx2x_release_hw_lock(bp, bnx2x_get_leader_lock_resource(bp)); |
| } |
| |
| int bnx2x_release_hw_lock(struct bnx2x *bp, u32 resource) |
| { |
| u32 lock_status; |
| u32 resource_bit = (1 << resource); |
| int func = BP_FUNC(bp); |
| u32 hw_lock_control_reg; |
| |
| /* Validating that the resource is within range */ |
| if (resource > HW_LOCK_MAX_RESOURCE_VALUE) { |
| BNX2X_ERR("resource(0x%x) > HW_LOCK_MAX_RESOURCE_VALUE(0x%x)\n", |
| resource, HW_LOCK_MAX_RESOURCE_VALUE); |
| return -EINVAL; |
| } |
| |
| if (func <= 5) { |
| hw_lock_control_reg = (MISC_REG_DRIVER_CONTROL_1 + func*8); |
| } else { |
| hw_lock_control_reg = |
| (MISC_REG_DRIVER_CONTROL_7 + (func - 6)*8); |
| } |
| |
| /* Validating that the resource is currently taken */ |
| lock_status = REG_RD(bp, hw_lock_control_reg); |
| if (!(lock_status & resource_bit)) { |
| BNX2X_ERR("lock_status 0x%x resource_bit 0x%x. Unlock was called but lock wasn't taken!\n", |
| lock_status, resource_bit); |
| return -EFAULT; |
| } |
| |
| REG_WR(bp, hw_lock_control_reg, resource_bit); |
| return 0; |
| } |
| |
| int bnx2x_get_gpio(struct bnx2x *bp, int gpio_num, u8 port) |
| { |
| /* The GPIO should be swapped if swap register is set and active */ |
| int gpio_port = (REG_RD(bp, NIG_REG_PORT_SWAP) && |
| REG_RD(bp, NIG_REG_STRAP_OVERRIDE)) ^ port; |
| int gpio_shift = gpio_num + |
| (gpio_port ? MISC_REGISTERS_GPIO_PORT_SHIFT : 0); |
| u32 gpio_mask = (1 << gpio_shift); |
| u32 gpio_reg; |
| int value; |
| |
| if (gpio_num > MISC_REGISTERS_GPIO_3) { |
| BNX2X_ERR("Invalid GPIO %d\n", gpio_num); |
| return -EINVAL; |
| } |
| |
| /* read GPIO value */ |
| gpio_reg = REG_RD(bp, MISC_REG_GPIO); |
| |
| /* get the requested pin value */ |
| if ((gpio_reg & gpio_mask) == gpio_mask) |
| value = 1; |
| else |
| value = 0; |
| |
| return value; |
| } |
| |
| int bnx2x_set_gpio(struct bnx2x *bp, int gpio_num, u32 mode, u8 port) |
| { |
| /* The GPIO should be swapped if swap register is set and active */ |
| int gpio_port = (REG_RD(bp, NIG_REG_PORT_SWAP) && |
| REG_RD(bp, NIG_REG_STRAP_OVERRIDE)) ^ port; |
| int gpio_shift = gpio_num + |
| (gpio_port ? MISC_REGISTERS_GPIO_PORT_SHIFT : 0); |
| u32 gpio_mask = (1 << gpio_shift); |
| u32 gpio_reg; |
| |
| if (gpio_num > MISC_REGISTERS_GPIO_3) { |
| BNX2X_ERR("Invalid GPIO %d\n", gpio_num); |
| return -EINVAL; |
| } |
| |
| bnx2x_acquire_hw_lock(bp, HW_LOCK_RESOURCE_GPIO); |
| /* read GPIO and mask except the float bits */ |
| gpio_reg = (REG_RD(bp, MISC_REG_GPIO) & MISC_REGISTERS_GPIO_FLOAT); |
| |
| switch (mode) { |
| case MISC_REGISTERS_GPIO_OUTPUT_LOW: |
| DP(NETIF_MSG_LINK, |
| "Set GPIO %d (shift %d) -> output low\n", |
| gpio_num, gpio_shift); |
| /* clear FLOAT and set CLR */ |
| gpio_reg &= ~(gpio_mask << MISC_REGISTERS_GPIO_FLOAT_POS); |
| gpio_reg |= (gpio_mask << MISC_REGISTERS_GPIO_CLR_POS); |
| break; |
| |
| case MISC_REGISTERS_GPIO_OUTPUT_HIGH: |
| DP(NETIF_MSG_LINK, |
| "Set GPIO %d (shift %d) -> output high\n", |
| gpio_num, gpio_shift); |
| /* clear FLOAT and set SET */ |
| gpio_reg &= ~(gpio_mask << MISC_REGISTERS_GPIO_FLOAT_POS); |
| gpio_reg |= (gpio_mask << MISC_REGISTERS_GPIO_SET_POS); |
| break; |
| |
| case MISC_REGISTERS_GPIO_INPUT_HI_Z: |
| DP(NETIF_MSG_LINK, |
| "Set GPIO %d (shift %d) -> input\n", |
| gpio_num, gpio_shift); |
| /* set FLOAT */ |
| gpio_reg |= (gpio_mask << MISC_REGISTERS_GPIO_FLOAT_POS); |
| break; |
| |
| default: |
| break; |
| } |
| |
| REG_WR(bp, MISC_REG_GPIO, gpio_reg); |
| bnx2x_release_hw_lock(bp, HW_LOCK_RESOURCE_GPIO); |
| |
| return 0; |
| } |
| |
| int bnx2x_set_mult_gpio(struct bnx2x *bp, u8 pins, u32 mode) |
| { |
| u32 gpio_reg = 0; |
| int rc = 0; |
| |
| /* Any port swapping should be handled by caller. */ |
| |
| bnx2x_acquire_hw_lock(bp, HW_LOCK_RESOURCE_GPIO); |
| /* read GPIO and mask except the float bits */ |
| gpio_reg = REG_RD(bp, MISC_REG_GPIO); |
| gpio_reg &= ~(pins << MISC_REGISTERS_GPIO_FLOAT_POS); |
| gpio_reg &= ~(pins << MISC_REGISTERS_GPIO_CLR_POS); |
| gpio_reg &= ~(pins << MISC_REGISTERS_GPIO_SET_POS); |
| |
| switch (mode) { |
| case MISC_REGISTERS_GPIO_OUTPUT_LOW: |
| DP(NETIF_MSG_LINK, "Set GPIO 0x%x -> output low\n", pins); |
| /* set CLR */ |
| gpio_reg |= (pins << MISC_REGISTERS_GPIO_CLR_POS); |
| break; |
| |
| case MISC_REGISTERS_GPIO_OUTPUT_HIGH: |
| DP(NETIF_MSG_LINK, "Set GPIO 0x%x -> output high\n", pins); |
| /* set SET */ |
| gpio_reg |= (pins << MISC_REGISTERS_GPIO_SET_POS); |
| break; |
| |
| case MISC_REGISTERS_GPIO_INPUT_HI_Z: |
| DP(NETIF_MSG_LINK, "Set GPIO 0x%x -> input\n", pins); |
| /* set FLOAT */ |
| gpio_reg |= (pins << MISC_REGISTERS_GPIO_FLOAT_POS); |
| break; |
| |
| default: |
| BNX2X_ERR("Invalid GPIO mode assignment %d\n", mode); |
| rc = -EINVAL; |
| break; |
| } |
| |
| if (rc == 0) |
| REG_WR(bp, MISC_REG_GPIO, gpio_reg); |
| |
| bnx2x_release_hw_lock(bp, HW_LOCK_RESOURCE_GPIO); |
| |
| return rc; |
| } |
| |
| int bnx2x_set_gpio_int(struct bnx2x *bp, int gpio_num, u32 mode, u8 port) |
| { |
| /* The GPIO should be swapped if swap register is set and active */ |
| int gpio_port = (REG_RD(bp, NIG_REG_PORT_SWAP) && |
| REG_RD(bp, NIG_REG_STRAP_OVERRIDE)) ^ port; |
| int gpio_shift = gpio_num + |
| (gpio_port ? MISC_REGISTERS_GPIO_PORT_SHIFT : 0); |
| u32 gpio_mask = (1 << gpio_shift); |
| u32 gpio_reg; |
| |
| if (gpio_num > MISC_REGISTERS_GPIO_3) { |
| BNX2X_ERR("Invalid GPIO %d\n", gpio_num); |
| return -EINVAL; |
| } |
| |
| bnx2x_acquire_hw_lock(bp, HW_LOCK_RESOURCE_GPIO); |
| /* read GPIO int */ |
| gpio_reg = REG_RD(bp, MISC_REG_GPIO_INT); |
| |
| switch (mode) { |
| case MISC_REGISTERS_GPIO_INT_OUTPUT_CLR: |
| DP(NETIF_MSG_LINK, |
| "Clear GPIO INT %d (shift %d) -> output low\n", |
| gpio_num, gpio_shift); |
| /* clear SET and set CLR */ |
| gpio_reg &= ~(gpio_mask << MISC_REGISTERS_GPIO_INT_SET_POS); |
| gpio_reg |= (gpio_mask << MISC_REGISTERS_GPIO_INT_CLR_POS); |
| break; |
| |
| case MISC_REGISTERS_GPIO_INT_OUTPUT_SET: |
| DP(NETIF_MSG_LINK, |
| "Set GPIO INT %d (shift %d) -> output high\n", |
| gpio_num, gpio_shift); |
| /* clear CLR and set SET */ |
| gpio_reg &= ~(gpio_mask << MISC_REGISTERS_GPIO_INT_CLR_POS); |
| gpio_reg |= (gpio_mask << MISC_REGISTERS_GPIO_INT_SET_POS); |
| break; |
| |
| default: |
| break; |
| } |
| |
| REG_WR(bp, MISC_REG_GPIO_INT, gpio_reg); |
| bnx2x_release_hw_lock(bp, HW_LOCK_RESOURCE_GPIO); |
| |
| return 0; |
| } |
| |
| static int bnx2x_set_spio(struct bnx2x *bp, int spio, u32 mode) |
| { |
| u32 spio_reg; |
| |
| /* Only 2 SPIOs are configurable */ |
| if ((spio != MISC_SPIO_SPIO4) && (spio != MISC_SPIO_SPIO5)) { |
| BNX2X_ERR("Invalid SPIO 0x%x\n", spio); |
| return -EINVAL; |
| } |
| |
| bnx2x_acquire_hw_lock(bp, HW_LOCK_RESOURCE_SPIO); |
| /* read SPIO and mask except the float bits */ |
| spio_reg = (REG_RD(bp, MISC_REG_SPIO) & MISC_SPIO_FLOAT); |
| |
| switch (mode) { |
| case MISC_SPIO_OUTPUT_LOW: |
| DP(NETIF_MSG_HW, "Set SPIO 0x%x -> output low\n", spio); |
| /* clear FLOAT and set CLR */ |
| spio_reg &= ~(spio << MISC_SPIO_FLOAT_POS); |
| spio_reg |= (spio << MISC_SPIO_CLR_POS); |
| break; |
| |
| case MISC_SPIO_OUTPUT_HIGH: |
| DP(NETIF_MSG_HW, "Set SPIO 0x%x -> output high\n", spio); |
| /* clear FLOAT and set SET */ |
| spio_reg &= ~(spio << MISC_SPIO_FLOAT_POS); |
| spio_reg |= (spio << MISC_SPIO_SET_POS); |
| break; |
| |
| case MISC_SPIO_INPUT_HI_Z: |
| DP(NETIF_MSG_HW, "Set SPIO 0x%x -> input\n", spio); |
| /* set FLOAT */ |
| spio_reg |= (spio << MISC_SPIO_FLOAT_POS); |
| break; |
| |
| default: |
| break; |
| } |
| |
| REG_WR(bp, MISC_REG_SPIO, spio_reg); |
| bnx2x_release_hw_lock(bp, HW_LOCK_RESOURCE_SPIO); |
| |
| return 0; |
| } |
| |
| void bnx2x_calc_fc_adv(struct bnx2x *bp) |
| { |
| u8 cfg_idx = bnx2x_get_link_cfg_idx(bp); |
| |
| bp->port.advertising[cfg_idx] &= ~(ADVERTISED_Asym_Pause | |
| ADVERTISED_Pause); |
| switch (bp->link_vars.ieee_fc & |
| MDIO_COMBO_IEEE0_AUTO_NEG_ADV_PAUSE_MASK) { |
| case MDIO_COMBO_IEEE0_AUTO_NEG_ADV_PAUSE_BOTH: |
| bp->port.advertising[cfg_idx] |= (ADVERTISED_Asym_Pause | |
| ADVERTISED_Pause); |
| break; |
| |
| case MDIO_COMBO_IEEE0_AUTO_NEG_ADV_PAUSE_ASYMMETRIC: |
| bp->port.advertising[cfg_idx] |= ADVERTISED_Asym_Pause; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| static void bnx2x_set_requested_fc(struct bnx2x *bp) |
| { |
| /* Initialize link parameters structure variables |
| * It is recommended to turn off RX FC for jumbo frames |
| * for better performance |
| */ |
| if (CHIP_IS_E1x(bp) && (bp->dev->mtu > 5000)) |
| bp->link_params.req_fc_auto_adv = BNX2X_FLOW_CTRL_TX; |
| else |
| bp->link_params.req_fc_auto_adv = BNX2X_FLOW_CTRL_BOTH; |
| } |
| |
| static void bnx2x_init_dropless_fc(struct bnx2x *bp) |
| { |
| u32 pause_enabled = 0; |
| |
| if (!CHIP_IS_E1(bp) && bp->dropless_fc && bp->link_vars.link_up) { |
| if (bp->link_vars.flow_ctrl & BNX2X_FLOW_CTRL_TX) |
| pause_enabled = 1; |
| |
| REG_WR(bp, BAR_USTRORM_INTMEM + |
| USTORM_ETH_PAUSE_ENABLED_OFFSET(BP_PORT(bp)), |
| pause_enabled); |
| } |
| |
| DP(NETIF_MSG_IFUP | NETIF_MSG_LINK, "dropless_fc is %s\n", |
| pause_enabled ? "enabled" : "disabled"); |
| } |
| |
| int bnx2x_initial_phy_init(struct bnx2x *bp, int load_mode) |
| { |
| int rc, cfx_idx = bnx2x_get_link_cfg_idx(bp); |
| u16 req_line_speed = bp->link_params.req_line_speed[cfx_idx]; |
| |
| if (!BP_NOMCP(bp)) { |
| bnx2x_set_requested_fc(bp); |
| bnx2x_acquire_phy_lock(bp); |
| |
| if (load_mode == LOAD_DIAG) { |
| struct link_params *lp = &bp->link_params; |
| lp->loopback_mode = LOOPBACK_XGXS; |
| /* Prefer doing PHY loopback at highest speed */ |
| if (lp->req_line_speed[cfx_idx] < SPEED_20000) { |
| if (lp->speed_cap_mask[cfx_idx] & |
| PORT_HW_CFG_SPEED_CAPABILITY_D0_20G) |
| lp->req_line_speed[cfx_idx] = |
| SPEED_20000; |
| else if (lp->speed_cap_mask[cfx_idx] & |
| PORT_HW_CFG_SPEED_CAPABILITY_D0_10G) |
| lp->req_line_speed[cfx_idx] = |
| SPEED_10000; |
| else |
| lp->req_line_speed[cfx_idx] = |
| SPEED_1000; |
| } |
| } |
| |
| if (load_mode == LOAD_LOOPBACK_EXT) { |
| struct link_params *lp = &bp->link_params; |
| lp->loopback_mode = LOOPBACK_EXT; |
| } |
| |
| rc = bnx2x_phy_init(&bp->link_params, &bp->link_vars); |
| |
| bnx2x_release_phy_lock(bp); |
| |
| bnx2x_init_dropless_fc(bp); |
| |
| bnx2x_calc_fc_adv(bp); |
| |
| if (bp->link_vars.link_up) { |
| bnx2x_stats_handle(bp, STATS_EVENT_LINK_UP); |
| bnx2x_link_report(bp); |
| } |
| queue_delayed_work(bnx2x_wq, &bp->period_task, 0); |
| bp->link_params.req_line_speed[cfx_idx] = req_line_speed; |
| return rc; |
| } |
| BNX2X_ERR("Bootcode is missing - can not initialize link\n"); |
| return -EINVAL; |
| } |
| |
| void bnx2x_link_set(struct bnx2x *bp) |
| { |
| if (!BP_NOMCP(bp)) { |
| bnx2x_acquire_phy_lock(bp); |
| bnx2x_phy_init(&bp->link_params, &bp->link_vars); |
| bnx2x_release_phy_lock(bp); |
| |
| bnx2x_init_dropless_fc(bp); |
| |
| bnx2x_calc_fc_adv(bp); |
| } else |
| BNX2X_ERR("Bootcode is missing - can not set link\n"); |
| } |
| |
| static void bnx2x__link_reset(struct bnx2x *bp) |
| { |
| if (!BP_NOMCP(bp)) { |
| bnx2x_acquire_phy_lock(bp); |
| bnx2x_lfa_reset(&bp->link_params, &bp->link_vars); |
| bnx2x_release_phy_lock(bp); |
| } else |
| BNX2X_ERR("Bootcode is missing - can not reset link\n"); |
| } |
| |
| void bnx2x_force_link_reset(struct bnx2x *bp) |
| { |
| bnx2x_acquire_phy_lock(bp); |
| bnx2x_link_reset(&bp->link_params, &bp->link_vars, 1); |
| bnx2x_release_phy_lock(bp); |
| } |
| |
| u8 bnx2x_link_test(struct bnx2x *bp, u8 is_serdes) |
| { |
| u8 rc = 0; |
| |
| if (!BP_NOMCP(bp)) { |
| bnx2x_acquire_phy_lock(bp); |
| rc = bnx2x_test_link(&bp->link_params, &bp->link_vars, |
| is_serdes); |
| bnx2x_release_phy_lock(bp); |
| } else |
| BNX2X_ERR("Bootcode is missing - can not test link\n"); |
| |
| return rc; |
| } |
| |
| /* Calculates the sum of vn_min_rates. |
| It's needed for further normalizing of the min_rates. |
| Returns: |
| sum of vn_min_rates. |
| or |
| 0 - if all the min_rates are 0. |
| In the later case fairness algorithm should be deactivated. |
| If not all min_rates are zero then those that are zeroes will be set to 1. |
| */ |
| static void bnx2x_calc_vn_min(struct bnx2x *bp, |
| struct cmng_init_input *input) |
| { |
| int all_zero = 1; |
| int vn; |
| |
| for (vn = VN_0; vn < BP_MAX_VN_NUM(bp); vn++) { |
| u32 vn_cfg = bp->mf_config[vn]; |
| u32 vn_min_rate = ((vn_cfg & FUNC_MF_CFG_MIN_BW_MASK) >> |
| FUNC_MF_CFG_MIN_BW_SHIFT) * 100; |
| |
| /* Skip hidden vns */ |
| if (vn_cfg & FUNC_MF_CFG_FUNC_HIDE) |
| vn_min_rate = 0; |
| /* If min rate is zero - set it to 1 */ |
| else if (!vn_min_rate) |
| vn_min_rate = DEF_MIN_RATE; |
| else |
| all_zero = 0; |
| |
| input->vnic_min_rate[vn] = vn_min_rate; |
| } |
| |
| /* if ETS or all min rates are zeros - disable fairness */ |
| if (BNX2X_IS_ETS_ENABLED(bp)) { |
| input->flags.cmng_enables &= |
| ~CMNG_FLAGS_PER_PORT_FAIRNESS_VN; |
| DP(NETIF_MSG_IFUP, "Fairness will be disabled due to ETS\n"); |
| } else if (all_zero) { |
| input->flags.cmng_enables &= |
| ~CMNG_FLAGS_PER_PORT_FAIRNESS_VN; |
| DP(NETIF_MSG_IFUP, |
| "All MIN values are zeroes fairness will be disabled\n"); |
| } else |
| input->flags.cmng_enables |= |
| CMNG_FLAGS_PER_PORT_FAIRNESS_VN; |
| } |
| |
| static void bnx2x_calc_vn_max(struct bnx2x *bp, int vn, |
| struct cmng_init_input *input) |
| { |
| u16 vn_max_rate; |
| u32 vn_cfg = bp->mf_config[vn]; |
| |
| if (vn_cfg & FUNC_MF_CFG_FUNC_HIDE) |
| vn_max_rate = 0; |
| else { |
| u32 maxCfg = bnx2x_extract_max_cfg(bp, vn_cfg); |
| |
| if (IS_MF_PERCENT_BW(bp)) { |
| /* maxCfg in percents of linkspeed */ |
| vn_max_rate = (bp->link_vars.line_speed * maxCfg) / 100; |
| } else /* SD modes */ |
| /* maxCfg is absolute in 100Mb units */ |
| vn_max_rate = maxCfg * 100; |
| } |
| |
| DP(NETIF_MSG_IFUP, "vn %d: vn_max_rate %d\n", vn, vn_max_rate); |
| |
| input->vnic_max_rate[vn] = vn_max_rate; |
| } |
| |
| static int bnx2x_get_cmng_fns_mode(struct bnx2x *bp) |
| { |
| if (CHIP_REV_IS_SLOW(bp)) |
| return CMNG_FNS_NONE; |
| if (IS_MF(bp)) |
| return CMNG_FNS_MINMAX; |
| |
| return CMNG_FNS_NONE; |
| } |
| |
| void bnx2x_read_mf_cfg(struct bnx2x *bp) |
| { |
| int vn, n = (CHIP_MODE_IS_4_PORT(bp) ? 2 : 1); |
| |
| if (BP_NOMCP(bp)) |
| return; /* what should be the default value in this case */ |
| |
| /* For 2 port configuration the absolute function number formula |
| * is: |
| * abs_func = 2 * vn + BP_PORT + BP_PATH |
| * |
| * and there are 4 functions per port |
| * |
| * For 4 port configuration it is |
| * abs_func = 4 * vn + 2 * BP_PORT + BP_PATH |
| * |
| * and there are 2 functions per port |
| */ |
| for (vn = VN_0; vn < BP_MAX_VN_NUM(bp); vn++) { |
| int /*abs*/func = n * (2 * vn + BP_PORT(bp)) + BP_PATH(bp); |
| |
| if (func >= E1H_FUNC_MAX) |
| break; |
| |
| bp->mf_config[vn] = |
| MF_CFG_RD(bp, func_mf_config[func].config); |
| } |
| if (bp->mf_config[BP_VN(bp)] & FUNC_MF_CFG_FUNC_DISABLED) { |
| DP(NETIF_MSG_IFUP, "mf_cfg function disabled\n"); |
| bp->flags |= MF_FUNC_DIS; |
| } else { |
| DP(NETIF_MSG_IFUP, "mf_cfg function enabled\n"); |
| bp->flags &= ~MF_FUNC_DIS; |
| } |
| } |
| |
| static void bnx2x_cmng_fns_init(struct bnx2x *bp, u8 read_cfg, u8 cmng_type) |
| { |
| struct cmng_init_input input; |
| memset(&input, 0, sizeof(struct cmng_init_input)); |
| |
| input.port_rate = bp->link_vars.line_speed; |
| |
| if (cmng_type == CMNG_FNS_MINMAX && input.port_rate) { |
| int vn; |
| |
| /* read mf conf from shmem */ |
| if (read_cfg) |
| bnx2x_read_mf_cfg(bp); |
| |
| /* vn_weight_sum and enable fairness if not 0 */ |
| bnx2x_calc_vn_min(bp, &input); |
| |
| /* calculate and set min-max rate for each vn */ |
| if (bp->port.pmf) |
| for (vn = VN_0; vn < BP_MAX_VN_NUM(bp); vn++) |
| bnx2x_calc_vn_max(bp, vn, &input); |
| |
| /* always enable rate shaping and fairness */ |
| input.flags.cmng_enables |= |
| CMNG_FLAGS_PER_PORT_RATE_SHAPING_VN; |
| |
| bnx2x_init_cmng(&input, &bp->cmng); |
| return; |
| } |
| |
| /* rate shaping and fairness are disabled */ |
| DP(NETIF_MSG_IFUP, |
| "rate shaping and fairness are disabled\n"); |
| } |
| |
| static void storm_memset_cmng(struct bnx2x *bp, |
| struct cmng_init *cmng, |
| u8 port) |
| { |
| int vn; |
| size_t size = sizeof(struct cmng_struct_per_port); |
| |
| u32 addr = BAR_XSTRORM_INTMEM + |
| XSTORM_CMNG_PER_PORT_VARS_OFFSET(port); |
| |
| __storm_memset_struct(bp, addr, size, (u32 *)&cmng->port); |
| |
| for (vn = VN_0; vn < BP_MAX_VN_NUM(bp); vn++) { |
| int func = func_by_vn(bp, vn); |
| |
| addr = BAR_XSTRORM_INTMEM + |
| XSTORM_RATE_SHAPING_PER_VN_VARS_OFFSET(func); |
| size = sizeof(struct rate_shaping_vars_per_vn); |
| __storm_memset_struct(bp, addr, size, |
| (u32 *)&cmng->vnic.vnic_max_rate[vn]); |
| |
| addr = BAR_XSTRORM_INTMEM + |
| XSTORM_FAIRNESS_PER_VN_VARS_OFFSET(func); |
| size = sizeof(struct fairness_vars_per_vn); |
| __storm_memset_struct(bp, addr, size, |
| (u32 *)&cmng->vnic.vnic_min_rate[vn]); |
| } |
| } |
| |
| /* init cmng mode in HW according to local configuration */ |
| void bnx2x_set_local_cmng(struct bnx2x *bp) |
| { |
| int cmng_fns = bnx2x_get_cmng_fns_mode(bp); |
| |
| if (cmng_fns != CMNG_FNS_NONE) { |
| bnx2x_cmng_fns_init(bp, false, cmng_fns); |
| storm_memset_cmng(bp, &bp->cmng, BP_PORT(bp)); |
| } else { |
| /* rate shaping and fairness are disabled */ |
| DP(NETIF_MSG_IFUP, |
| "single function mode without fairness\n"); |
| } |
| } |
| |
| /* This function is called upon link interrupt */ |
| static void bnx2x_link_attn(struct bnx2x *bp) |
| { |
| /* Make sure that we are synced with the current statistics */ |
| bnx2x_stats_handle(bp, STATS_EVENT_STOP); |
| |
| bnx2x_link_update(&bp->link_params, &bp->link_vars); |
| |
| bnx2x_init_dropless_fc(bp); |
| |
| if (bp->link_vars.link_up) { |
| |
| if (bp->link_vars.mac_type != MAC_TYPE_EMAC) { |
| struct host_port_stats *pstats; |
| |
| pstats = bnx2x_sp(bp, port_stats); |
| /* reset old mac stats */ |
| memset(&(pstats->mac_stx[0]), 0, |
| sizeof(struct mac_stx)); |
| } |
| if (bp->state == BNX2X_STATE_OPEN) |
| bnx2x_stats_handle(bp, STATS_EVENT_LINK_UP); |
| } |
| |
| if (bp->link_vars.link_up && bp->link_vars.line_speed) |
| bnx2x_set_local_cmng(bp); |
| |
| __bnx2x_link_report(bp); |
| |
| if (IS_MF(bp)) |
| bnx2x_link_sync_notify(bp); |
| } |
| |
| void bnx2x__link_status_update(struct bnx2x *bp) |
| { |
| if (bp->state != BNX2X_STATE_OPEN) |
| return; |
| |
| /* read updated dcb configuration */ |
| if (IS_PF(bp)) { |
| bnx2x_dcbx_pmf_update(bp); |
| bnx2x_link_status_update(&bp->link_params, &bp->link_vars); |
| if (bp->link_vars.link_up) |
| bnx2x_stats_handle(bp, STATS_EVENT_LINK_UP); |
| else |
| bnx2x_stats_handle(bp, STATS_EVENT_STOP); |
| /* indicate link status */ |
| bnx2x_link_report(bp); |
| |
| } else { /* VF */ |
| bp->port.supported[0] |= (SUPPORTED_10baseT_Half | |
| SUPPORTED_10baseT_Full | |
| SUPPORTED_100baseT_Half | |
| SUPPORTED_100baseT_Full | |
| SUPPORTED_1000baseT_Full | |
| SUPPORTED_2500baseX_Full | |
| SUPPORTED_10000baseT_Full | |
| SUPPORTED_TP | |
| SUPPORTED_FIBRE | |
| SUPPORTED_Autoneg | |
| SUPPORTED_Pause | |
| SUPPORTED_Asym_Pause); |
| bp->port.advertising[0] = bp->port.supported[0]; |
| |
| bp->link_params.bp = bp; |
| bp->link_params.port = BP_PORT(bp); |
| bp->link_params.req_duplex[0] = DUPLEX_FULL; |
| bp->link_params.req_flow_ctrl[0] = BNX2X_FLOW_CTRL_NONE; |
| bp->link_params.req_line_speed[0] = SPEED_10000; |
| bp->link_params.speed_cap_mask[0] = 0x7f0000; |
| bp->link_params.switch_cfg = SWITCH_CFG_10G; |
| bp->link_vars.mac_type = MAC_TYPE_BMAC; |
| bp->link_vars.line_speed = SPEED_10000; |
| bp->link_vars.link_status = |
| (LINK_STATUS_LINK_UP | |
| LINK_STATUS_SPEED_AND_DUPLEX_10GTFD); |
| bp->link_vars.link_up = 1; |
| bp->link_vars.duplex = DUPLEX_FULL; |
| bp->link_vars.flow_ctrl = BNX2X_FLOW_CTRL_NONE; |
| __bnx2x_link_report(bp); |
| |
| bnx2x_sample_bulletin(bp); |
| |
| /* if bulletin board did not have an update for link status |
| * __bnx2x_link_report will report current status |
| * but it will NOT duplicate report in case of already reported |
| * during sampling bulletin board. |
| */ |
| bnx2x_stats_handle(bp, STATS_EVENT_LINK_UP); |
| } |
| } |
| |
| static int bnx2x_afex_func_update(struct bnx2x *bp, u16 vifid, |
| u16 vlan_val, u8 allowed_prio) |
| { |
| struct bnx2x_func_state_params func_params = {NULL}; |
| struct bnx2x_func_afex_update_params *f_update_params = |
| &func_params.params.afex_update; |
| |
| func_params.f_obj = &bp->func_obj; |
| func_params.cmd = BNX2X_F_CMD_AFEX_UPDATE; |
| |
| /* no need to wait for RAMROD completion, so don't |
| * set RAMROD_COMP_WAIT flag |
| */ |
| |
| f_update_params->vif_id = vifid; |
| f_update_params->afex_default_vlan = vlan_val; |
| f_update_params->allowed_priorities = allowed_prio; |
| |
| /* if ramrod can not be sent, response to MCP immediately */ |
| if (bnx2x_func_state_change(bp, &func_params) < 0) |
| bnx2x_fw_command(bp, DRV_MSG_CODE_AFEX_VIFSET_ACK, 0); |
| |
| return 0; |
| } |
| |
| static int bnx2x_afex_handle_vif_list_cmd(struct bnx2x *bp, u8 cmd_type, |
| u16 vif_index, u8 func_bit_map) |
| { |
| struct bnx2x_func_state_params func_params = {NULL}; |
| struct bnx2x_func_afex_viflists_params *update_params = |
| &func_params.params.afex_viflists; |
| int rc; |
| u32 drv_msg_code; |
| |
| /* validate only LIST_SET and LIST_GET are received from switch */ |
| if ((cmd_type != VIF_LIST_RULE_GET) && (cmd_type != VIF_LIST_RULE_SET)) |
| BNX2X_ERR("BUG! afex_handle_vif_list_cmd invalid type 0x%x\n", |
| cmd_type); |
| |
| func_params.f_obj = &bp->func_obj; |
| func_params.cmd = BNX2X_F_CMD_AFEX_VIFLISTS; |
| |
| /* set parameters according to cmd_type */ |
| update_params->afex_vif_list_command = cmd_type; |
| update_params->vif_list_index = vif_index; |
| update_params->func_bit_map = |
| (cmd_type == VIF_LIST_RULE_GET) ? 0 : func_bit_map; |
| update_params->func_to_clear = 0; |
| drv_msg_code = |
| (cmd_type == VIF_LIST_RULE_GET) ? |
| DRV_MSG_CODE_AFEX_LISTGET_ACK : |
| DRV_MSG_CODE_AFEX_LISTSET_ACK; |
| |
| /* if ramrod can not be sent, respond to MCP immediately for |
| * SET and GET requests (other are not triggered from MCP) |
| */ |
| rc = bnx2x_func_state_change(bp, &func_params); |
| if (rc < 0) |
| bnx2x_fw_command(bp, drv_msg_code, 0); |
| |
| return 0; |
| } |
| |
| static void bnx2x_handle_afex_cmd(struct bnx2x *bp, u32 cmd) |
| { |
| struct afex_stats afex_stats; |
| u32 func = BP_ABS_FUNC(bp); |
| u32 mf_config; |
| u16 vlan_val; |
| u32 vlan_prio; |
| u16 vif_id; |
| u8 allowed_prio; |
| u8 vlan_mode; |
| u32 addr_to_write, vifid, addrs, stats_type, i; |
| |
| if (cmd & DRV_STATUS_AFEX_LISTGET_REQ) { |
| vifid = SHMEM2_RD(bp, afex_param1_to_driver[BP_FW_MB_IDX(bp)]); |
| DP(BNX2X_MSG_MCP, |
| "afex: got MCP req LISTGET_REQ for vifid 0x%x\n", vifid); |
| bnx2x_afex_handle_vif_list_cmd(bp, VIF_LIST_RULE_GET, vifid, 0); |
| } |
| |
| if (cmd & DRV_STATUS_AFEX_LISTSET_REQ) { |
| vifid = SHMEM2_RD(bp, afex_param1_to_driver[BP_FW_MB_IDX(bp)]); |
| addrs = SHMEM2_RD(bp, afex_param2_to_driver[BP_FW_MB_IDX(bp)]); |
| DP(BNX2X_MSG_MCP, |
| "afex: got MCP req LISTSET_REQ for vifid 0x%x addrs 0x%x\n", |
| vifid, addrs); |
| bnx2x_afex_handle_vif_list_cmd(bp, VIF_LIST_RULE_SET, vifid, |
| addrs); |
| } |
| |
| if (cmd & DRV_STATUS_AFEX_STATSGET_REQ) { |
| addr_to_write = SHMEM2_RD(bp, |
| afex_scratchpad_addr_to_write[BP_FW_MB_IDX(bp)]); |
| stats_type = SHMEM2_RD(bp, |
| afex_param1_to_driver[BP_FW_MB_IDX(bp)]); |
| |
| DP(BNX2X_MSG_MCP, |
| "afex: got MCP req STATSGET_REQ, write to addr 0x%x\n", |
| addr_to_write); |
| |
| bnx2x_afex_collect_stats(bp, (void *)&afex_stats, stats_type); |
| |
| /* write response to scratchpad, for MCP */ |
| for (i = 0; i < (sizeof(struct afex_stats)/sizeof(u32)); i++) |
| REG_WR(bp, addr_to_write + i*sizeof(u32), |
| *(((u32 *)(&afex_stats))+i)); |
| |
| /* send ack message to MCP */ |
| bnx2x_fw_command(bp, DRV_MSG_CODE_AFEX_STATSGET_ACK, 0); |
| } |
| |
| if (cmd & DRV_STATUS_AFEX_VIFSET_REQ) { |
| mf_config = MF_CFG_RD(bp, func_mf_config[func].config); |
| bp->mf_config[BP_VN(bp)] = mf_config; |
| DP(BNX2X_MSG_MCP, |
| "afex: got MCP req VIFSET_REQ, mf_config 0x%x\n", |
| mf_config); |
| |
| /* if VIF_SET is "enabled" */ |
| if (!(mf_config & FUNC_MF_CFG_FUNC_DISABLED)) { |
| /* set rate limit directly to internal RAM */ |
| struct cmng_init_input cmng_input; |
| struct rate_shaping_vars_per_vn m_rs_vn; |
| size_t size = sizeof(struct rate_shaping_vars_per_vn); |
| u32 addr = BAR_XSTRORM_INTMEM + |
| XSTORM_RATE_SHAPING_PER_VN_VARS_OFFSET(BP_FUNC(bp)); |
| |
| bp->mf_config[BP_VN(bp)] = mf_config; |
| |
| bnx2x_calc_vn_max(bp, BP_VN(bp), &cmng_input); |
| m_rs_vn.vn_counter.rate = |
| cmng_input.vnic_max_rate[BP_VN(bp)]; |
| m_rs_vn.vn_counter.quota = |
| (m_rs_vn.vn_counter.rate * |
| RS_PERIODIC_TIMEOUT_USEC) / 8; |
| |
| __storm_memset_struct(bp, addr, size, (u32 *)&m_rs_vn); |
| |
| /* read relevant values from mf_cfg struct in shmem */ |
| vif_id = |
| (MF_CFG_RD(bp, func_mf_config[func].e1hov_tag) & |
| FUNC_MF_CFG_E1HOV_TAG_MASK) >> |
| FUNC_MF_CFG_E1HOV_TAG_SHIFT; |
| vlan_val = |
| (MF_CFG_RD(bp, func_mf_config[func].e1hov_tag) & |
| FUNC_MF_CFG_AFEX_VLAN_MASK) >> |
| FUNC_MF_CFG_AFEX_VLAN_SHIFT; |
| vlan_prio = (mf_config & |
| FUNC_MF_CFG_TRANSMIT_PRIORITY_MASK) >> |
| FUNC_MF_CFG_TRANSMIT_PRIORITY_SHIFT; |
| vlan_val |= (vlan_prio << VLAN_PRIO_SHIFT); |
| vlan_mode = |
| (MF_CFG_RD(bp, |
| func_mf_config[func].afex_config) & |
| FUNC_MF_CFG_AFEX_VLAN_MODE_MASK) >> |
| FUNC_MF_CFG_AFEX_VLAN_MODE_SHIFT; |
| allowed_prio = |
| (MF_CFG_RD(bp, |
| func_mf_config[func].afex_config) & |
| FUNC_MF_CFG_AFEX_COS_FILTER_MASK) >> |
| FUNC_MF_CFG_AFEX_COS_FILTER_SHIFT; |
| |
| /* send ramrod to FW, return in case of failure */ |
| if (bnx2x_afex_func_update(bp, vif_id, vlan_val, |
| allowed_prio)) |
| return; |
| |
| bp->afex_def_vlan_tag = vlan_val; |
| bp->afex_vlan_mode = vlan_mode; |
| } else { |
| /* notify link down because BP->flags is disabled */ |
| bnx2x_link_report(bp); |
| |
| /* send INVALID VIF ramrod to FW */ |
| bnx2x_afex_func_update(bp, 0xFFFF, 0, 0); |
| |
| /* Reset the default afex VLAN */ |
| bp->afex_def_vlan_tag = -1; |
| } |
| } |
| } |
| |
| static void bnx2x_handle_update_svid_cmd(struct bnx2x *bp) |
| { |
| struct bnx2x_func_switch_update_params *switch_update_params; |
| struct bnx2x_func_state_params func_params; |
| |
| memset(&func_params, 0, sizeof(struct bnx2x_func_state_params)); |
| switch_update_params = &func_params.params.switch_update; |
| func_params.f_obj = &bp->func_obj; |
| func_params.cmd = BNX2X_F_CMD_SWITCH_UPDATE; |
| |
| /* Prepare parameters for function state transitions */ |
| __set_bit(RAMROD_COMP_WAIT, &func_params.ramrod_flags); |
| __set_bit(RAMROD_RETRY, &func_params.ramrod_flags); |
| |
| if (IS_MF_UFP(bp) || IS_MF_BD(bp)) { |
| int func = BP_ABS_FUNC(bp); |
| u32 val; |
| |
| /* Re-learn the S-tag from shmem */ |
| val = MF_CFG_RD(bp, func_mf_config[func].e1hov_tag) & |
| FUNC_MF_CFG_E1HOV_TAG_MASK; |
| if (val != FUNC_MF_CFG_E1HOV_TAG_DEFAULT) { |
| bp->mf_ov = val; |
| } else { |
| BNX2X_ERR("Got an SVID event, but no tag is configured in shmem\n"); |
| goto fail; |
| } |
| |
| /* Configure new S-tag in LLH */ |
| REG_WR(bp, NIG_REG_LLH0_FUNC_VLAN_ID + BP_PORT(bp) * 8, |
| bp->mf_ov); |
| |
| /* Send Ramrod to update FW of change */ |
| __set_bit(BNX2X_F_UPDATE_SD_VLAN_TAG_CHNG, |
| &switch_update_params->changes); |
| switch_update_params->vlan = bp->mf_ov; |
| |
| if (bnx2x_func_state_change(bp, &func_params) < 0) { |
| BNX2X_ERR("Failed to configure FW of S-tag Change to %02x\n", |
| bp->mf_ov); |
| goto fail; |
| } else { |
| DP(BNX2X_MSG_MCP, "Configured S-tag %02x\n", |
| bp->mf_ov); |
| } |
| } else { |
| goto fail; |
| } |
| |
| bnx2x_fw_command(bp, DRV_MSG_CODE_OEM_UPDATE_SVID_OK, 0); |
| return; |
| fail: |
| bnx2x_fw_command(bp, DRV_MSG_CODE_OEM_UPDATE_SVID_FAILURE, 0); |
| } |
| |
| static void bnx2x_pmf_update(struct bnx2x *bp) |
| { |
| int port = BP_PORT(bp); |
| u32 val; |
| |
| bp->port.pmf = 1; |
| DP(BNX2X_MSG_MCP, "pmf %d\n", bp->port.pmf); |
| |
| /* |
| * We need the mb() to ensure the ordering between the writing to |
| * bp->port.pmf here and reading it from the bnx2x_periodic_task(). |
| */ |
| smp_mb(); |
| |
| /* queue a periodic task */ |
| queue_delayed_work(bnx2x_wq, &bp->period_task, 0); |
| |
| bnx2x_dcbx_pmf_update(bp); |
| |
| /* enable nig attention */ |
| val = (0xff0f | (1 << (BP_VN(bp) + 4))); |
| if (bp->common.int_block == INT_BLOCK_HC) { |
| REG_WR(bp, HC_REG_TRAILING_EDGE_0 + port*8, val); |
| REG_WR(bp, HC_REG_LEADING_EDGE_0 + port*8, val); |
| } else if (!CHIP_IS_E1x(bp)) { |
| REG_WR(bp, IGU_REG_TRAILING_EDGE_LATCH, val); |
| REG_WR(bp, IGU_REG_LEADING_EDGE_LATCH, val); |
| } |
| |
| bnx2x_stats_handle(bp, STATS_EVENT_PMF); |
| } |
| |
| /* end of Link */ |
| |
| /* slow path */ |
| |
| /* |
| * General service functions |
| */ |
| |
| /* send the MCP a request, block until there is a reply */ |
| u32 bnx2x_fw_command(struct bnx2x *bp, u32 command, u32 param) |
| { |
| int mb_idx = BP_FW_MB_IDX(bp); |
| u32 seq; |
| u32 rc = 0; |
| u32 cnt = 1; |
| u8 delay = CHIP_REV_IS_SLOW(bp) ? 100 : 10; |
| |
| mutex_lock(&bp->fw_mb_mutex); |
| seq = ++bp->fw_seq; |
| SHMEM_WR(bp, func_mb[mb_idx].drv_mb_param, param); |
| SHMEM_WR(bp, func_mb[mb_idx].drv_mb_header, (command | seq)); |
| |
| DP(BNX2X_MSG_MCP, "wrote command (%x) to FW MB param 0x%08x\n", |
| (command | seq), param); |
| |
| do { |
| /* let the FW do it's magic ... */ |
| msleep(delay); |
| |
| rc = SHMEM_RD(bp, func_mb[mb_idx].fw_mb_header); |
| |
| /* Give the FW up to 5 second (500*10ms) */ |
| } while ((seq != (rc & FW_MSG_SEQ_NUMBER_MASK)) && (cnt++ < 500)); |
| |
| DP(BNX2X_MSG_MCP, "[after %d ms] read (%x) seq is (%x) from FW MB\n", |
| cnt*delay, rc, seq); |
| |
| /* is this a reply to our command? */ |
| if (seq == (rc & FW_MSG_SEQ_NUMBER_MASK)) |
| rc &= FW_MSG_CODE_MASK; |
| else { |
| /* FW BUG! */ |
| BNX2X_ERR("FW failed to respond!\n"); |
| bnx2x_fw_dump(bp); |
| rc = 0; |
| } |
| mutex_unlock(&bp->fw_mb_mutex); |
| |
| return rc; |
| } |
| |
| static void storm_memset_func_cfg(struct bnx2x *bp, |
| struct tstorm_eth_function_common_config *tcfg, |
| u16 abs_fid) |
| { |
| size_t size = sizeof(struct tstorm_eth_function_common_config); |
| |
| u32 addr = BAR_TSTRORM_INTMEM + |
| TSTORM_FUNCTION_COMMON_CONFIG_OFFSET(abs_fid); |
| |
| __storm_memset_struct(bp, addr, size, (u32 *)tcfg); |
| } |
| |
| void bnx2x_func_init(struct bnx2x *bp, struct bnx2x_func_init_params *p) |
| { |
| if (CHIP_IS_E1x(bp)) { |
| struct tstorm_eth_function_common_config tcfg = {0}; |
| |
| storm_memset_func_cfg(bp, &tcfg, p->func_id); |
| } |
| |
| /* Enable the function in the FW */ |
| storm_memset_vf_to_pf(bp, p->func_id, p->pf_id); |
| storm_memset_func_en(bp, p->func_id, 1); |
| |
| /* spq */ |
| if (p->spq_active) { |
| storm_memset_spq_addr(bp, p->spq_map, p->func_id); |
| REG_WR(bp, XSEM_REG_FAST_MEMORY + |
| XSTORM_SPQ_PROD_OFFSET(p->func_id), p->spq_prod); |
| } |
| } |
| |
| /** |
| * bnx2x_get_common_flags - Return common flags |
|