| /* bnx2x_main.c: Broadcom Everest network driver. |
| * |
| * Copyright (c) 2007-2010 Broadcom Corporation |
| * |
| * 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: Eilon Greenstein <eilong@broadcom.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 |
| * |
| */ |
| |
| #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/vmalloc.h> |
| #include <linux/interrupt.h> |
| #include <linux/pci.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 <net/ip.h> |
| #include <net/tcp.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/stringify.h> |
| |
| |
| #include "bnx2x.h" |
| #include "bnx2x_init.h" |
| #include "bnx2x_init_ops.h" |
| #include "bnx2x_dump.h" |
| |
| #define DRV_MODULE_VERSION "1.52.53-2" |
| #define DRV_MODULE_RELDATE "2010/21/07" |
| #define BNX2X_BC_VER 0x040200 |
| |
| #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_NAME_E1 "bnx2x-e1-" FW_FILE_VERSION ".fw" |
| #define FW_FILE_NAME_E1H "bnx2x-e1h-" FW_FILE_VERSION ".fw" |
| |
| /* Time in jiffies before concluding the transmitter is hung */ |
| #define TX_TIMEOUT (5*HZ) |
| |
| static char version[] __devinitdata = |
| "Broadcom NetXtreme II 5771x 10Gigabit Ethernet Driver " |
| DRV_MODULE_NAME " " DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n"; |
| |
| MODULE_AUTHOR("Eliezer Tamir"); |
| MODULE_DESCRIPTION("Broadcom NetXtreme II BCM57710/57711/57711E Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION(DRV_MODULE_VERSION); |
| MODULE_FIRMWARE(FW_FILE_NAME_E1); |
| MODULE_FIRMWARE(FW_FILE_NAME_E1H); |
| |
| static int multi_mode = 1; |
| module_param(multi_mode, int, 0); |
| MODULE_PARM_DESC(multi_mode, " Multi queue mode " |
| "(0 Disable; 1 Enable (default))"); |
| |
| static int num_queues; |
| module_param(num_queues, int, 0); |
| MODULE_PARM_DESC(num_queues, " Number of queues for multi_mode=1" |
| " (default is as a number of CPUs)"); |
| |
| static int disable_tpa; |
| module_param(disable_tpa, int, 0); |
| MODULE_PARM_DESC(disable_tpa, " Disable the TPA (LRO) feature"); |
| |
| static int int_mode; |
| module_param(int_mode, int, 0); |
| MODULE_PARM_DESC(int_mode, " Force interrupt mode other then MSI-X " |
| "(1 INT#x; 2 MSI)"); |
| |
| static int dropless_fc; |
| module_param(dropless_fc, int, 0); |
| MODULE_PARM_DESC(dropless_fc, " Pause on exhausted host ring"); |
| |
| static int poll; |
| module_param(poll, int, 0); |
| MODULE_PARM_DESC(poll, " Use polling (for debug)"); |
| |
| static int mrrs = -1; |
| module_param(mrrs, int, 0); |
| MODULE_PARM_DESC(mrrs, " Force Max Read Req Size (0..3) (for debug)"); |
| |
| static int debug; |
| module_param(debug, int, 0); |
| MODULE_PARM_DESC(debug, " Default debug msglevel"); |
| |
| static int load_count[3]; /* 0-common, 1-port0, 2-port1 */ |
| |
| static struct workqueue_struct *bnx2x_wq; |
| |
| enum bnx2x_board_type { |
| BCM57710 = 0, |
| BCM57711 = 1, |
| BCM57711E = 2, |
| }; |
| |
| /* indexed by board_type, above */ |
| static struct { |
| char *name; |
| } board_info[] __devinitdata = { |
| { "Broadcom NetXtreme II BCM57710 XGb" }, |
| { "Broadcom NetXtreme II BCM57711 XGb" }, |
| { "Broadcom NetXtreme II BCM57711E XGb" } |
| }; |
| |
| |
| static DEFINE_PCI_DEVICE_TABLE(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 }, |
| { 0 } |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, bnx2x_pci_tbl); |
| |
| /**************************************************************************** |
| * General service functions |
| ****************************************************************************/ |
| |
| /* used only at init |
| * locking is done by mcp |
| */ |
| 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; |
| } |
| |
| static 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 |
| }; |
| |
| /* copy command into DMAE command memory and set DMAE command go */ |
| static 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)); |
| |
| DP(BNX2X_MSG_OFF, "DMAE cmd[%d].%d (0x%08x) : 0x%08x\n", |
| idx, i, cmd_offset + i*4, *(((u32 *)dmae) + i)); |
| } |
| REG_WR(bp, dmae_reg_go_c[idx], 1); |
| } |
| |
| void bnx2x_write_dmae(struct bnx2x *bp, dma_addr_t dma_addr, u32 dst_addr, |
| u32 len32) |
| { |
| struct dmae_command dmae; |
| u32 *wb_comp = bnx2x_sp(bp, wb_comp); |
| int cnt = 200; |
| |
| if (!bp->dmae_ready) { |
| u32 *data = bnx2x_sp(bp, wb_data[0]); |
| |
| DP(BNX2X_MSG_OFF, "DMAE is not ready (dst_addr %08x len32 %d)" |
| " using indirect\n", dst_addr, len32); |
| bnx2x_init_ind_wr(bp, dst_addr, data, len32); |
| return; |
| } |
| |
| memset(&dmae, 0, sizeof(struct dmae_command)); |
| |
| dmae.opcode = (DMAE_CMD_SRC_PCI | DMAE_CMD_DST_GRC | |
| DMAE_CMD_C_DST_PCI | DMAE_CMD_C_ENABLE | |
| DMAE_CMD_SRC_RESET | DMAE_CMD_DST_RESET | |
| #ifdef __BIG_ENDIAN |
| DMAE_CMD_ENDIANITY_B_DW_SWAP | |
| #else |
| DMAE_CMD_ENDIANITY_DW_SWAP | |
| #endif |
| (BP_PORT(bp) ? DMAE_CMD_PORT_1 : DMAE_CMD_PORT_0) | |
| (BP_E1HVN(bp) << DMAE_CMD_E1HVN_SHIFT)); |
| 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; |
| 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; |
| |
| DP(BNX2X_MSG_OFF, "DMAE: opcode 0x%08x\n" |
| DP_LEVEL "src_addr [%x:%08x] len [%d *4] " |
| "dst_addr [%x:%08x (%08x)]\n" |
| DP_LEVEL "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, dst_addr, |
| dmae.comp_addr_hi, dmae.comp_addr_lo, dmae.comp_val); |
| DP(BNX2X_MSG_OFF, "data [0x%08x 0x%08x 0x%08x 0x%08x]\n", |
| bp->slowpath->wb_data[0], bp->slowpath->wb_data[1], |
| bp->slowpath->wb_data[2], bp->slowpath->wb_data[3]); |
| |
| mutex_lock(&bp->dmae_mutex); |
| |
| *wb_comp = 0; |
| |
| bnx2x_post_dmae(bp, &dmae, INIT_DMAE_C(bp)); |
| |
| udelay(5); |
| |
| while (*wb_comp != DMAE_COMP_VAL) { |
| DP(BNX2X_MSG_OFF, "wb_comp 0x%08x\n", *wb_comp); |
| |
| if (!cnt) { |
| BNX2X_ERR("DMAE timeout!\n"); |
| break; |
| } |
| cnt--; |
| /* adjust delay for emulation/FPGA */ |
| if (CHIP_REV_IS_SLOW(bp)) |
| msleep(100); |
| else |
| udelay(5); |
| } |
| |
| mutex_unlock(&bp->dmae_mutex); |
| } |
| |
| void bnx2x_read_dmae(struct bnx2x *bp, u32 src_addr, u32 len32) |
| { |
| struct dmae_command dmae; |
| u32 *wb_comp = bnx2x_sp(bp, wb_comp); |
| int cnt = 200; |
| |
| if (!bp->dmae_ready) { |
| u32 *data = bnx2x_sp(bp, wb_data[0]); |
| int i; |
| |
| DP(BNX2X_MSG_OFF, "DMAE is not ready (src_addr %08x len32 %d)" |
| " using indirect\n", src_addr, len32); |
| for (i = 0; i < len32; i++) |
| data[i] = bnx2x_reg_rd_ind(bp, src_addr + i*4); |
| return; |
| } |
| |
| memset(&dmae, 0, sizeof(struct dmae_command)); |
| |
| dmae.opcode = (DMAE_CMD_SRC_GRC | DMAE_CMD_DST_PCI | |
| DMAE_CMD_C_DST_PCI | DMAE_CMD_C_ENABLE | |
| DMAE_CMD_SRC_RESET | DMAE_CMD_DST_RESET | |
| #ifdef __BIG_ENDIAN |
| DMAE_CMD_ENDIANITY_B_DW_SWAP | |
| #else |
| DMAE_CMD_ENDIANITY_DW_SWAP | |
| #endif |
| (BP_PORT(bp) ? DMAE_CMD_PORT_1 : DMAE_CMD_PORT_0) | |
| (BP_E1HVN(bp) << DMAE_CMD_E1HVN_SHIFT)); |
| 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; |
| 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; |
| |
| DP(BNX2X_MSG_OFF, "DMAE: opcode 0x%08x\n" |
| DP_LEVEL "src_addr [%x:%08x] len [%d *4] " |
| "dst_addr [%x:%08x (%08x)]\n" |
| DP_LEVEL "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, src_addr, |
| dmae.comp_addr_hi, dmae.comp_addr_lo, dmae.comp_val); |
| |
| mutex_lock(&bp->dmae_mutex); |
| |
| memset(bnx2x_sp(bp, wb_data[0]), 0, sizeof(u32) * 4); |
| *wb_comp = 0; |
| |
| bnx2x_post_dmae(bp, &dmae, INIT_DMAE_C(bp)); |
| |
| udelay(5); |
| |
| while (*wb_comp != DMAE_COMP_VAL) { |
| |
| if (!cnt) { |
| BNX2X_ERR("DMAE timeout!\n"); |
| break; |
| } |
| cnt--; |
| /* adjust delay for emulation/FPGA */ |
| if (CHIP_REV_IS_SLOW(bp)) |
| msleep(100); |
| else |
| udelay(5); |
| } |
| DP(BNX2X_MSG_OFF, "data [0x%08x 0x%08x 0x%08x 0x%08x]\n", |
| bp->slowpath->wb_data[0], bp->slowpath->wb_data[1], |
| bp->slowpath->wb_data[2], bp->slowpath->wb_data[3]); |
| |
| mutex_unlock(&bp->dmae_mutex); |
| } |
| |
| 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); |
| } |
| |
| /* used only for slowpath so not inlined */ |
| static void bnx2x_wb_wr(struct bnx2x *bp, int reg, u32 val_hi, u32 val_lo) |
| { |
| u32 wb_write[2]; |
| |
| wb_write[0] = val_hi; |
| wb_write[1] = val_lo; |
| REG_WR_DMAE(bp, reg, wb_write, 2); |
| } |
| |
| #ifdef USE_WB_RD |
| static u64 bnx2x_wb_rd(struct bnx2x *bp, int reg) |
| { |
| u32 wb_data[2]; |
| |
| REG_RD_DMAE(bp, reg, wb_data, 2); |
| |
| return HILO_U64(wb_data[0], wb_data[1]); |
| } |
| #endif |
| |
| static int bnx2x_mc_assert(struct bnx2x *bp) |
| { |
| char last_idx; |
| int i, rc = 0; |
| u32 row0, row1, row2, row3; |
| |
| /* XSTORM */ |
| last_idx = REG_RD8(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_ASSERT_LIST_INDEX_OFFSET); |
| if (last_idx) |
| BNX2X_ERR("XSTORM_ASSERT_LIST_INDEX 0x%x\n", last_idx); |
| |
| /* print the asserts */ |
| for (i = 0; i < STROM_ASSERT_ARRAY_SIZE; i++) { |
| |
| row0 = REG_RD(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_ASSERT_LIST_OFFSET(i)); |
| row1 = REG_RD(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_ASSERT_LIST_OFFSET(i) + 4); |
| row2 = REG_RD(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_ASSERT_LIST_OFFSET(i) + 8); |
| row3 = REG_RD(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_ASSERT_LIST_OFFSET(i) + 12); |
| |
| if (row0 != COMMON_ASM_INVALID_ASSERT_OPCODE) { |
| BNX2X_ERR("XSTORM_ASSERT_INDEX 0x%x = 0x%08x" |
| " 0x%08x 0x%08x 0x%08x\n", |
| i, row3, row2, row1, row0); |
| rc++; |
| } else { |
| break; |
| } |
| } |
| |
| /* TSTORM */ |
| last_idx = REG_RD8(bp, BAR_TSTRORM_INTMEM + |
| TSTORM_ASSERT_LIST_INDEX_OFFSET); |
| if (last_idx) |
| BNX2X_ERR("TSTORM_ASSERT_LIST_INDEX 0x%x\n", last_idx); |
| |
| /* print the asserts */ |
| for (i = 0; i < STROM_ASSERT_ARRAY_SIZE; i++) { |
| |
| row0 = REG_RD(bp, BAR_TSTRORM_INTMEM + |
| TSTORM_ASSERT_LIST_OFFSET(i)); |
| row1 = REG_RD(bp, BAR_TSTRORM_INTMEM + |
| TSTORM_ASSERT_LIST_OFFSET(i) + 4); |
| row2 = REG_RD(bp, BAR_TSTRORM_INTMEM + |
| TSTORM_ASSERT_LIST_OFFSET(i) + 8); |
| row3 = REG_RD(bp, BAR_TSTRORM_INTMEM + |
| TSTORM_ASSERT_LIST_OFFSET(i) + 12); |
| |
| if (row0 != COMMON_ASM_INVALID_ASSERT_OPCODE) { |
| BNX2X_ERR("TSTORM_ASSERT_INDEX 0x%x = 0x%08x" |
| " 0x%08x 0x%08x 0x%08x\n", |
| i, row3, row2, row1, row0); |
| rc++; |
| } else { |
| break; |
| } |
| } |
| |
| /* CSTORM */ |
| last_idx = REG_RD8(bp, BAR_CSTRORM_INTMEM + |
| CSTORM_ASSERT_LIST_INDEX_OFFSET); |
| if (last_idx) |
| BNX2X_ERR("CSTORM_ASSERT_LIST_INDEX 0x%x\n", last_idx); |
| |
| /* print the asserts */ |
| for (i = 0; i < STROM_ASSERT_ARRAY_SIZE; i++) { |
| |
| row0 = REG_RD(bp, BAR_CSTRORM_INTMEM + |
| CSTORM_ASSERT_LIST_OFFSET(i)); |
| row1 = REG_RD(bp, BAR_CSTRORM_INTMEM + |
| CSTORM_ASSERT_LIST_OFFSET(i) + 4); |
| row2 = REG_RD(bp, BAR_CSTRORM_INTMEM + |
| CSTORM_ASSERT_LIST_OFFSET(i) + 8); |
| row3 = REG_RD(bp, BAR_CSTRORM_INTMEM + |
| CSTORM_ASSERT_LIST_OFFSET(i) + 12); |
| |
| if (row0 != COMMON_ASM_INVALID_ASSERT_OPCODE) { |
| BNX2X_ERR("CSTORM_ASSERT_INDEX 0x%x = 0x%08x" |
| " 0x%08x 0x%08x 0x%08x\n", |
| i, row3, row2, row1, row0); |
| rc++; |
| } else { |
| break; |
| } |
| } |
| |
| /* USTORM */ |
| last_idx = REG_RD8(bp, BAR_USTRORM_INTMEM + |
| USTORM_ASSERT_LIST_INDEX_OFFSET); |
| if (last_idx) |
| BNX2X_ERR("USTORM_ASSERT_LIST_INDEX 0x%x\n", last_idx); |
| |
| /* print the asserts */ |
| for (i = 0; i < STROM_ASSERT_ARRAY_SIZE; i++) { |
| |
| row0 = REG_RD(bp, BAR_USTRORM_INTMEM + |
| USTORM_ASSERT_LIST_OFFSET(i)); |
| row1 = REG_RD(bp, BAR_USTRORM_INTMEM + |
| USTORM_ASSERT_LIST_OFFSET(i) + 4); |
| row2 = REG_RD(bp, BAR_USTRORM_INTMEM + |
| USTORM_ASSERT_LIST_OFFSET(i) + 8); |
| row3 = REG_RD(bp, BAR_USTRORM_INTMEM + |
| USTORM_ASSERT_LIST_OFFSET(i) + 12); |
| |
| if (row0 != COMMON_ASM_INVALID_ASSERT_OPCODE) { |
| BNX2X_ERR("USTORM_ASSERT_INDEX 0x%x = 0x%08x" |
| " 0x%08x 0x%08x 0x%08x\n", |
| i, row3, row2, row1, row0); |
| rc++; |
| } else { |
| break; |
| } |
| } |
| |
| return rc; |
| } |
| |
| static void bnx2x_fw_dump(struct bnx2x *bp) |
| { |
| u32 addr; |
| u32 mark, offset; |
| __be32 data[9]; |
| int word; |
| |
| if (BP_NOMCP(bp)) { |
| BNX2X_ERR("NO MCP - can not dump\n"); |
| return; |
| } |
| |
| addr = bp->common.shmem_base - 0x0800 + 4; |
| mark = REG_RD(bp, addr); |
| mark = MCP_REG_MCPR_SCRATCH + ((mark + 0x3) & ~0x3) - 0x08000000; |
| pr_err("begin fw dump (mark 0x%x)\n", mark); |
| |
| pr_err(""); |
| for (offset = mark; offset <= bp->common.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); |
| } |
| 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); |
| } |
| pr_err("end of fw dump\n"); |
| } |
| |
| static void bnx2x_panic_dump(struct bnx2x *bp) |
| { |
| int i; |
| u16 j, start, end; |
| |
| bp->stats_state = STATS_STATE_DISABLED; |
| DP(BNX2X_MSG_STATS, "stats_state - DISABLED\n"); |
| |
| BNX2X_ERR("begin crash dump -----------------\n"); |
| |
| /* Indices */ |
| /* Common */ |
| BNX2X_ERR("def_c_idx(0x%x) def_u_idx(0x%x) def_x_idx(0x%x)" |
| " def_t_idx(0x%x) def_att_idx(0x%x) attn_state(0x%x)" |
| " spq_prod_idx(0x%x)\n", |
| bp->def_c_idx, bp->def_u_idx, bp->def_x_idx, bp->def_t_idx, |
| bp->def_att_idx, bp->attn_state, bp->spq_prod_idx); |
| |
| /* Rx */ |
| for_each_queue(bp, i) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| |
| BNX2X_ERR("fp%d: rx_bd_prod(0x%x) rx_bd_cons(0x%x)" |
| " *rx_bd_cons_sb(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, |
| le16_to_cpu(*fp->rx_bd_cons_sb), 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_u_idx(0x%x) *sb_u_idx(0x%x)\n", |
| fp->rx_sge_prod, fp->last_max_sge, |
| le16_to_cpu(fp->fp_u_idx), |
| fp->status_blk->u_status_block.status_block_index); |
| } |
| |
| /* Tx */ |
| for_each_queue(bp, i) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| |
| 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, fp->tx_pkt_prod, fp->tx_pkt_cons, fp->tx_bd_prod, |
| fp->tx_bd_cons, le16_to_cpu(*fp->tx_cons_sb)); |
| BNX2X_ERR(" fp_c_idx(0x%x) *sb_c_idx(0x%x)" |
| " tx_db_prod(0x%x)\n", le16_to_cpu(fp->fp_c_idx), |
| fp->status_blk->c_status_block.status_block_index, |
| fp->tx_db.data.prod); |
| } |
| |
| /* Rings */ |
| /* Rx */ |
| for_each_queue(bp, i) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| |
| 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->skb); |
| } |
| |
| 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_queue(bp, i) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| |
| start = TX_BD(le16_to_cpu(*fp->tx_cons_sb) - 10); |
| end = TX_BD(le16_to_cpu(*fp->tx_cons_sb) + 245); |
| for (j = start; j != end; j = TX_BD(j + 1)) { |
| struct sw_tx_bd *sw_bd = &fp->tx_buf_ring[j]; |
| |
| BNX2X_ERR("fp%d: packet[%x]=[%p,%x]\n", |
| i, j, sw_bd->skb, sw_bd->first_bd); |
| } |
| |
| start = TX_BD(fp->tx_bd_cons - 10); |
| end = TX_BD(fp->tx_bd_cons + 254); |
| for (j = start; j != end; j = TX_BD(j + 1)) { |
| u32 *tx_bd = (u32 *)&fp->tx_desc_ring[j]; |
| |
| BNX2X_ERR("fp%d: tx_bd[%x]=[%x:%x:%x:%x]\n", |
| i, j, tx_bd[0], tx_bd[1], tx_bd[2], tx_bd[3]); |
| } |
| } |
| |
| bnx2x_fw_dump(bp); |
| bnx2x_mc_assert(bp); |
| BNX2X_ERR("end crash dump -----------------\n"); |
| } |
| |
| static void bnx2x_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); |
| int msix = (bp->flags & USING_MSIX_FLAG) ? 1 : 0; |
| int msi = (bp->flags & USING_MSI_FLAG) ? 1 : 0; |
| |
| 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); |
| } 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); |
| |
| DP(NETIF_MSG_INTR, "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; |
| } |
| |
| DP(NETIF_MSG_INTR, "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 |
| */ |
| mmiowb(); |
| barrier(); |
| |
| if (CHIP_IS_E1H(bp)) { |
| /* init leading/trailing edge */ |
| if (IS_E1HMF(bp)) { |
| val = (0xee0f | (1 << (BP_E1HVN(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); |
| } |
| |
| /* Make sure that interrupts are indeed enabled from here on */ |
| mmiowb(); |
| } |
| |
| static void bnx2x_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); |
| |
| 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_INTR, "write %x to HC %d (addr 0x%x)\n", |
| val, port, addr); |
| |
| /* flush all outstanding writes */ |
| mmiowb(); |
| |
| REG_WR(bp, addr, val); |
| if (REG_RD(bp, addr) != val) |
| BNX2X_ERR("BUG! proper val not read from IGU!\n"); |
| } |
| |
| static void bnx2x_int_disable_sync(struct bnx2x *bp, int disable_hw) |
| { |
| int msix = (bp->flags & USING_MSIX_FLAG) ? 1 : 0; |
| int i, offset; |
| |
| /* disable interrupt handling */ |
| atomic_inc(&bp->intr_sem); |
| smp_wmb(); /* Ensure that bp->intr_sem update is SMP-safe */ |
| |
| 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; |
| #ifdef BCM_CNIC |
| offset++; |
| #endif |
| for_each_queue(bp, i) |
| synchronize_irq(bp->msix_table[i + offset].vector); |
| } else |
| synchronize_irq(bp->pdev->irq); |
| |
| /* make sure sp_task is not running */ |
| cancel_delayed_work(&bp->sp_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, "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, |
| "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); |
| |
| /* 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, "Failed to get a lock on resource %d\n", resource); |
| return false; |
| } |
| |
| static inline void bnx2x_ack_sb(struct bnx2x *bp, u8 sb_id, |
| u8 storm, u16 index, u8 op, u8 update) |
| { |
| u32 hc_addr = (HC_REG_COMMAND_REG + BP_PORT(bp)*32 + |
| COMMAND_REG_INT_ACK); |
| struct igu_ack_register igu_ack; |
| |
| igu_ack.status_block_index = index; |
| igu_ack.sb_id_and_flags = |
| ((sb_id << IGU_ACK_REGISTER_STATUS_BLOCK_ID_SHIFT) | |
| (storm << IGU_ACK_REGISTER_STORM_ID_SHIFT) | |
| (update << IGU_ACK_REGISTER_UPDATE_INDEX_SHIFT) | |
| (op << IGU_ACK_REGISTER_INTERRUPT_MODE_SHIFT)); |
| |
| DP(BNX2X_MSG_OFF, "write 0x%08x to HC addr 0x%x\n", |
| (*(u32 *)&igu_ack), hc_addr); |
| REG_WR(bp, hc_addr, (*(u32 *)&igu_ack)); |
| |
| /* Make sure that ACK is written */ |
| mmiowb(); |
| barrier(); |
| } |
| |
| static inline void bnx2x_update_fpsb_idx(struct bnx2x_fastpath *fp) |
| { |
| struct host_status_block *fpsb = fp->status_blk; |
| |
| barrier(); /* status block is written to by the chip */ |
| fp->fp_c_idx = fpsb->c_status_block.status_block_index; |
| fp->fp_u_idx = fpsb->u_status_block.status_block_index; |
| } |
| |
| static u16 bnx2x_ack_int(struct bnx2x *bp) |
| { |
| u32 hc_addr = (HC_REG_COMMAND_REG + BP_PORT(bp)*32 + |
| COMMAND_REG_SIMD_MASK); |
| u32 result = REG_RD(bp, hc_addr); |
| |
| DP(BNX2X_MSG_OFF, "read 0x%08x from HC addr 0x%x\n", |
| result, hc_addr); |
| |
| return result; |
| } |
| |
| |
| /* |
| * fast path service functions |
| */ |
| |
| static inline int bnx2x_has_tx_work_unload(struct bnx2x_fastpath *fp) |
| { |
| /* Tell compiler that consumer and producer can change */ |
| barrier(); |
| return (fp->tx_pkt_prod != fp->tx_pkt_cons); |
| } |
| |
| /* free skb in the packet ring at pos idx |
| * return idx of last bd freed |
| */ |
| static u16 bnx2x_free_tx_pkt(struct bnx2x *bp, struct bnx2x_fastpath *fp, |
| u16 idx) |
| { |
| struct sw_tx_bd *tx_buf = &fp->tx_buf_ring[idx]; |
| struct eth_tx_start_bd *tx_start_bd; |
| struct eth_tx_bd *tx_data_bd; |
| struct sk_buff *skb = tx_buf->skb; |
| u16 bd_idx = TX_BD(tx_buf->first_bd), new_cons; |
| int nbd; |
| |
| /* prefetch skb end pointer to speedup dev_kfree_skb() */ |
| prefetch(&skb->end); |
| |
| DP(BNX2X_MSG_OFF, "pkt_idx %d buff @(%p)->skb %p\n", |
| idx, tx_buf, skb); |
| |
| /* unmap first bd */ |
| DP(BNX2X_MSG_OFF, "free bd_idx %d\n", bd_idx); |
| tx_start_bd = &fp->tx_desc_ring[bd_idx].start_bd; |
| dma_unmap_single(&bp->pdev->dev, BD_UNMAP_ADDR(tx_start_bd), |
| BD_UNMAP_LEN(tx_start_bd), PCI_DMA_TODEVICE); |
| |
| nbd = le16_to_cpu(tx_start_bd->nbd) - 1; |
| #ifdef BNX2X_STOP_ON_ERROR |
| if ((nbd - 1) > (MAX_SKB_FRAGS + 2)) { |
| BNX2X_ERR("BAD nbd!\n"); |
| bnx2x_panic(); |
| } |
| #endif |
| new_cons = nbd + tx_buf->first_bd; |
| |
| /* Get the next bd */ |
| bd_idx = TX_BD(NEXT_TX_IDX(bd_idx)); |
| |
| /* Skip a parse bd... */ |
| --nbd; |
| bd_idx = TX_BD(NEXT_TX_IDX(bd_idx)); |
| |
| /* ...and the TSO split header bd since they have no mapping */ |
| if (tx_buf->flags & BNX2X_TSO_SPLIT_BD) { |
| --nbd; |
| bd_idx = TX_BD(NEXT_TX_IDX(bd_idx)); |
| } |
| |
| /* now free frags */ |
| while (nbd > 0) { |
| |
| DP(BNX2X_MSG_OFF, "free frag bd_idx %d\n", bd_idx); |
| tx_data_bd = &fp->tx_desc_ring[bd_idx].reg_bd; |
| dma_unmap_page(&bp->pdev->dev, BD_UNMAP_ADDR(tx_data_bd), |
| BD_UNMAP_LEN(tx_data_bd), DMA_TO_DEVICE); |
| if (--nbd) |
| bd_idx = TX_BD(NEXT_TX_IDX(bd_idx)); |
| } |
| |
| /* release skb */ |
| WARN_ON(!skb); |
| dev_kfree_skb(skb); |
| tx_buf->first_bd = 0; |
| tx_buf->skb = NULL; |
| |
| return new_cons; |
| } |
| |
| static inline u16 bnx2x_tx_avail(struct bnx2x_fastpath *fp) |
| { |
| s16 used; |
| u16 prod; |
| u16 cons; |
| |
| prod = fp->tx_bd_prod; |
| cons = fp->tx_bd_cons; |
| |
| /* NUM_TX_RINGS = number of "next-page" entries |
| It will be used as a threshold */ |
| used = SUB_S16(prod, cons) + (s16)NUM_TX_RINGS; |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| WARN_ON(used < 0); |
| WARN_ON(used > fp->bp->tx_ring_size); |
| WARN_ON((fp->bp->tx_ring_size - used) > MAX_TX_AVAIL); |
| #endif |
| |
| return (s16)(fp->bp->tx_ring_size) - used; |
| } |
| |
| static inline int bnx2x_has_tx_work(struct bnx2x_fastpath *fp) |
| { |
| u16 hw_cons; |
| |
| /* Tell compiler that status block fields can change */ |
| barrier(); |
| hw_cons = le16_to_cpu(*fp->tx_cons_sb); |
| return hw_cons != fp->tx_pkt_cons; |
| } |
| |
| static int bnx2x_tx_int(struct bnx2x_fastpath *fp) |
| { |
| struct bnx2x *bp = fp->bp; |
| struct netdev_queue *txq; |
| u16 hw_cons, sw_cons, bd_cons = fp->tx_bd_cons; |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (unlikely(bp->panic)) |
| return -1; |
| #endif |
| |
| txq = netdev_get_tx_queue(bp->dev, fp->index); |
| hw_cons = le16_to_cpu(*fp->tx_cons_sb); |
| sw_cons = fp->tx_pkt_cons; |
| |
| while (sw_cons != hw_cons) { |
| u16 pkt_cons; |
| |
| pkt_cons = TX_BD(sw_cons); |
| |
| /* prefetch(bp->tx_buf_ring[pkt_cons].skb); */ |
| |
| DP(NETIF_MSG_TX_DONE, "hw_cons %u sw_cons %u pkt_cons %u\n", |
| hw_cons, sw_cons, pkt_cons); |
| |
| /* if (NEXT_TX_IDX(sw_cons) != hw_cons) { |
| rmb(); |
| prefetch(fp->tx_buf_ring[NEXT_TX_IDX(sw_cons)].skb); |
| } |
| */ |
| bd_cons = bnx2x_free_tx_pkt(bp, fp, pkt_cons); |
| sw_cons++; |
| } |
| |
| fp->tx_pkt_cons = sw_cons; |
| fp->tx_bd_cons = bd_cons; |
| |
| /* Need to make the tx_bd_cons update visible to start_xmit() |
| * before checking for netif_tx_queue_stopped(). Without the |
| * memory barrier, there is a small possibility that |
| * start_xmit() will miss it and cause the queue to be stopped |
| * forever. |
| */ |
| smp_mb(); |
| |
| /* TBD need a thresh? */ |
| if (unlikely(netif_tx_queue_stopped(txq))) { |
| /* Taking tx_lock() is needed to prevent reenabling the queue |
| * while it's empty. This could have happen if rx_action() gets |
| * suspended in bnx2x_tx_int() after the condition before |
| * netif_tx_wake_queue(), while tx_action (bnx2x_start_xmit()): |
| * |
| * stops the queue->sees fresh tx_bd_cons->releases the queue-> |
| * sends some packets consuming the whole queue again-> |
| * stops the queue |
| */ |
| |
| __netif_tx_lock(txq, smp_processor_id()); |
| |
| if ((netif_tx_queue_stopped(txq)) && |
| (bp->state == BNX2X_STATE_OPEN) && |
| (bnx2x_tx_avail(fp) >= MAX_SKB_FRAGS + 3)) |
| netif_tx_wake_queue(txq); |
| |
| __netif_tx_unlock(txq); |
| } |
| return 0; |
| } |
| |
| #ifdef BCM_CNIC |
| static void bnx2x_cnic_cfc_comp(struct bnx2x *bp, int cid); |
| #endif |
| |
| static 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); |
| |
| 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); |
| |
| bp->spq_left++; |
| |
| if (fp->index) { |
| switch (command | fp->state) { |
| case (RAMROD_CMD_ID_ETH_CLIENT_SETUP | |
| BNX2X_FP_STATE_OPENING): |
| DP(NETIF_MSG_IFUP, "got MULTI[%d] setup ramrod\n", |
| cid); |
| fp->state = BNX2X_FP_STATE_OPEN; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_HALT | BNX2X_FP_STATE_HALTING): |
| DP(NETIF_MSG_IFDOWN, "got MULTI[%d] halt ramrod\n", |
| cid); |
| fp->state = BNX2X_FP_STATE_HALTED; |
| break; |
| |
| default: |
| BNX2X_ERR("unexpected MC reply (%d) " |
| "fp[%d] state is %x\n", |
| command, fp->index, fp->state); |
| break; |
| } |
| mb(); /* force bnx2x_wait_ramrod() to see the change */ |
| return; |
| } |
| |
| switch (command | bp->state) { |
| case (RAMROD_CMD_ID_ETH_PORT_SETUP | BNX2X_STATE_OPENING_WAIT4_PORT): |
| DP(NETIF_MSG_IFUP, "got setup ramrod\n"); |
| bp->state = BNX2X_STATE_OPEN; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_HALT | BNX2X_STATE_CLOSING_WAIT4_HALT): |
| DP(NETIF_MSG_IFDOWN, "got halt ramrod\n"); |
| bp->state = BNX2X_STATE_CLOSING_WAIT4_DELETE; |
| fp->state = BNX2X_FP_STATE_HALTED; |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_CFC_DEL | BNX2X_STATE_CLOSING_WAIT4_HALT): |
| DP(NETIF_MSG_IFDOWN, "got delete ramrod for MULTI[%d]\n", cid); |
| bnx2x_fp(bp, cid, state) = BNX2X_FP_STATE_CLOSED; |
| break; |
| |
| #ifdef BCM_CNIC |
| case (RAMROD_CMD_ID_ETH_CFC_DEL | BNX2X_STATE_OPEN): |
| DP(NETIF_MSG_IFDOWN, "got delete ramrod for CID %d\n", cid); |
| bnx2x_cnic_cfc_comp(bp, cid); |
| break; |
| #endif |
| |
| case (RAMROD_CMD_ID_ETH_SET_MAC | BNX2X_STATE_OPEN): |
| case (RAMROD_CMD_ID_ETH_SET_MAC | BNX2X_STATE_DIAG): |
| DP(NETIF_MSG_IFUP, "got set mac ramrod\n"); |
| bp->set_mac_pending--; |
| smp_wmb(); |
| break; |
| |
| case (RAMROD_CMD_ID_ETH_SET_MAC | BNX2X_STATE_CLOSING_WAIT4_HALT): |
| DP(NETIF_MSG_IFDOWN, "got (un)set mac ramrod\n"); |
| bp->set_mac_pending--; |
| smp_wmb(); |
| break; |
| |
| default: |
| BNX2X_ERR("unexpected MC reply (%d) bp->state is %x\n", |
| command, bp->state); |
| break; |
| } |
| mb(); /* force bnx2x_wait_ramrod() to see the change */ |
| } |
| |
| static inline void bnx2x_free_rx_sge(struct bnx2x *bp, |
| struct bnx2x_fastpath *fp, u16 index) |
| { |
| struct sw_rx_page *sw_buf = &fp->rx_page_ring[index]; |
| struct page *page = sw_buf->page; |
| struct eth_rx_sge *sge = &fp->rx_sge_ring[index]; |
| |
| /* Skip "next page" elements */ |
| if (!page) |
| return; |
| |
| dma_unmap_page(&bp->pdev->dev, dma_unmap_addr(sw_buf, mapping), |
| SGE_PAGE_SIZE*PAGES_PER_SGE, PCI_DMA_FROMDEVICE); |
| __free_pages(page, PAGES_PER_SGE_SHIFT); |
| |
| sw_buf->page = NULL; |
| sge->addr_hi = 0; |
| sge->addr_lo = 0; |
| } |
| |
| static inline void bnx2x_free_rx_sge_range(struct bnx2x *bp, |
| struct bnx2x_fastpath *fp, int last) |
| { |
| int i; |
| |
| for (i = 0; i < last; i++) |
| bnx2x_free_rx_sge(bp, fp, i); |
| } |
| |
| static inline int bnx2x_alloc_rx_sge(struct bnx2x *bp, |
| struct bnx2x_fastpath *fp, u16 index) |
| { |
| struct page *page = alloc_pages(GFP_ATOMIC, PAGES_PER_SGE_SHIFT); |
| struct sw_rx_page *sw_buf = &fp->rx_page_ring[index]; |
| struct eth_rx_sge *sge = &fp->rx_sge_ring[index]; |
| dma_addr_t mapping; |
| |
| if (unlikely(page == NULL)) |
| return -ENOMEM; |
| |
| mapping = dma_map_page(&bp->pdev->dev, page, 0, |
| SGE_PAGE_SIZE*PAGES_PER_SGE, DMA_FROM_DEVICE); |
| if (unlikely(dma_mapping_error(&bp->pdev->dev, mapping))) { |
| __free_pages(page, PAGES_PER_SGE_SHIFT); |
| return -ENOMEM; |
| } |
| |
| sw_buf->page = page; |
| dma_unmap_addr_set(sw_buf, mapping, mapping); |
| |
| sge->addr_hi = cpu_to_le32(U64_HI(mapping)); |
| sge->addr_lo = cpu_to_le32(U64_LO(mapping)); |
| |
| return 0; |
| } |
| |
| static inline int bnx2x_alloc_rx_skb(struct bnx2x *bp, |
| struct bnx2x_fastpath *fp, u16 index) |
| { |
| struct sk_buff *skb; |
| struct sw_rx_bd *rx_buf = &fp->rx_buf_ring[index]; |
| struct eth_rx_bd *rx_bd = &fp->rx_desc_ring[index]; |
| dma_addr_t mapping; |
| |
| skb = netdev_alloc_skb(bp->dev, bp->rx_buf_size); |
| if (unlikely(skb == NULL)) |
| return -ENOMEM; |
| |
| mapping = dma_map_single(&bp->pdev->dev, skb->data, bp->rx_buf_size, |
| DMA_FROM_DEVICE); |
| if (unlikely(dma_mapping_error(&bp->pdev->dev, mapping))) { |
| dev_kfree_skb(skb); |
| return -ENOMEM; |
| } |
| |
| rx_buf->skb = skb; |
| dma_unmap_addr_set(rx_buf, mapping, mapping); |
| |
| rx_bd->addr_hi = cpu_to_le32(U64_HI(mapping)); |
| rx_bd->addr_lo = cpu_to_le32(U64_LO(mapping)); |
| |
| return 0; |
| } |
| |
| /* note that we are not allocating a new skb, |
| * we are just moving one from cons to prod |
| * we are not creating a new mapping, |
| * so there is no need to check for dma_mapping_error(). |
| */ |
| static void bnx2x_reuse_rx_skb(struct bnx2x_fastpath *fp, |
| struct sk_buff *skb, u16 cons, u16 prod) |
| { |
| struct bnx2x *bp = fp->bp; |
| struct sw_rx_bd *cons_rx_buf = &fp->rx_buf_ring[cons]; |
| struct sw_rx_bd *prod_rx_buf = &fp->rx_buf_ring[prod]; |
| struct eth_rx_bd *cons_bd = &fp->rx_desc_ring[cons]; |
| struct eth_rx_bd *prod_bd = &fp->rx_desc_ring[prod]; |
| |
| dma_sync_single_for_device(&bp->pdev->dev, |
| dma_unmap_addr(cons_rx_buf, mapping), |
| RX_COPY_THRESH, DMA_FROM_DEVICE); |
| |
| prod_rx_buf->skb = cons_rx_buf->skb; |
| dma_unmap_addr_set(prod_rx_buf, mapping, |
| dma_unmap_addr(cons_rx_buf, mapping)); |
| *prod_bd = *cons_bd; |
| } |
| |
| static inline void bnx2x_update_last_max_sge(struct bnx2x_fastpath *fp, |
| u16 idx) |
| { |
| u16 last_max = fp->last_max_sge; |
| |
| if (SUB_S16(idx, last_max) > 0) |
| fp->last_max_sge = idx; |
| } |
| |
| static void bnx2x_clear_sge_mask_next_elems(struct bnx2x_fastpath *fp) |
| { |
| int i, j; |
| |
| for (i = 1; i <= NUM_RX_SGE_PAGES; i++) { |
| int idx = RX_SGE_CNT * i - 1; |
| |
| for (j = 0; j < 2; j++) { |
| SGE_MASK_CLEAR_BIT(fp, idx); |
| idx--; |
| } |
| } |
| } |
| |
| static void bnx2x_update_sge_prod(struct bnx2x_fastpath *fp, |
| struct eth_fast_path_rx_cqe *fp_cqe) |
| { |
| struct bnx2x *bp = fp->bp; |
| u16 sge_len = SGE_PAGE_ALIGN(le16_to_cpu(fp_cqe->pkt_len) - |
| le16_to_cpu(fp_cqe->len_on_bd)) >> |
| SGE_PAGE_SHIFT; |
| u16 last_max, last_elem, first_elem; |
| u16 delta = 0; |
| u16 i; |
| |
| if (!sge_len) |
| return; |
| |
| /* First mark all used pages */ |
| for (i = 0; i < sge_len; i++) |
| SGE_MASK_CLEAR_BIT(fp, RX_SGE(le16_to_cpu(fp_cqe->sgl[i]))); |
| |
| DP(NETIF_MSG_RX_STATUS, "fp_cqe->sgl[%d] = %d\n", |
| sge_len - 1, le16_to_cpu(fp_cqe->sgl[sge_len - 1])); |
| |
| /* Here we assume that the last SGE index is the biggest */ |
| prefetch((void *)(fp->sge_mask)); |
| bnx2x_update_last_max_sge(fp, le16_to_cpu(fp_cqe->sgl[sge_len - 1])); |
| |
| last_max = RX_SGE(fp->last_max_sge); |
| last_elem = last_max >> RX_SGE_MASK_ELEM_SHIFT; |
| first_elem = RX_SGE(fp->rx_sge_prod) >> RX_SGE_MASK_ELEM_SHIFT; |
| |
| /* If ring is not full */ |
| if (last_elem + 1 != first_elem) |
| last_elem++; |
| |
| /* Now update the prod */ |
| for (i = first_elem; i != last_elem; i = NEXT_SGE_MASK_ELEM(i)) { |
| if (likely(fp->sge_mask[i])) |
| break; |
| |
| fp->sge_mask[i] = RX_SGE_MASK_ELEM_ONE_MASK; |
| delta += RX_SGE_MASK_ELEM_SZ; |
| } |
| |
| if (delta > 0) { |
| fp->rx_sge_prod += delta; |
| /* clear page-end entries */ |
| bnx2x_clear_sge_mask_next_elems(fp); |
| } |
| |
| DP(NETIF_MSG_RX_STATUS, |
| "fp->last_max_sge = %d fp->rx_sge_prod = %d\n", |
| fp->last_max_sge, fp->rx_sge_prod); |
| } |
| |
| static inline void bnx2x_init_sge_ring_bit_mask(struct bnx2x_fastpath *fp) |
| { |
| /* Set the mask to all 1-s: it's faster to compare to 0 than to 0xf-s */ |
| memset(fp->sge_mask, 0xff, |
| (NUM_RX_SGE >> RX_SGE_MASK_ELEM_SHIFT)*sizeof(u64)); |
| |
| /* Clear the two last indices in the page to 1: |
| these are the indices that correspond to the "next" element, |
| hence will never be indicated and should be removed from |
| the calculations. */ |
| bnx2x_clear_sge_mask_next_elems(fp); |
| } |
| |
| static void bnx2x_tpa_start(struct bnx2x_fastpath *fp, u16 queue, |
| struct sk_buff *skb, u16 cons, u16 prod) |
| { |
| struct bnx2x *bp = fp->bp; |
| struct sw_rx_bd *cons_rx_buf = &fp->rx_buf_ring[cons]; |
| struct sw_rx_bd *prod_rx_buf = &fp->rx_buf_ring[prod]; |
| struct eth_rx_bd *prod_bd = &fp->rx_desc_ring[prod]; |
| dma_addr_t mapping; |
| |
| /* move empty skb from pool to prod and map it */ |
| prod_rx_buf->skb = fp->tpa_pool[queue].skb; |
| mapping = dma_map_single(&bp->pdev->dev, fp->tpa_pool[queue].skb->data, |
| bp->rx_buf_size, DMA_FROM_DEVICE); |
| dma_unmap_addr_set(prod_rx_buf, mapping, mapping); |
| |
| /* move partial skb from cons to pool (don't unmap yet) */ |
| fp->tpa_pool[queue] = *cons_rx_buf; |
| |
| /* mark bin state as start - print error if current state != stop */ |
| if (fp->tpa_state[queue] != BNX2X_TPA_STOP) |
| BNX2X_ERR("start of bin not in stop [%d]\n", queue); |
| |
| fp->tpa_state[queue] = BNX2X_TPA_START; |
| |
| /* point prod_bd to new skb */ |
| prod_bd->addr_hi = cpu_to_le32(U64_HI(mapping)); |
| prod_bd->addr_lo = cpu_to_le32(U64_LO(mapping)); |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| fp->tpa_queue_used |= (1 << queue); |
| #ifdef _ASM_GENERIC_INT_L64_H |
| DP(NETIF_MSG_RX_STATUS, "fp->tpa_queue_used = 0x%lx\n", |
| #else |
| DP(NETIF_MSG_RX_STATUS, "fp->tpa_queue_used = 0x%llx\n", |
| #endif |
| fp->tpa_queue_used); |
| #endif |
| } |
| |
| static int bnx2x_fill_frag_skb(struct bnx2x *bp, struct bnx2x_fastpath *fp, |
| struct sk_buff *skb, |
| struct eth_fast_path_rx_cqe *fp_cqe, |
| u16 cqe_idx) |
| { |
| struct sw_rx_page *rx_pg, old_rx_pg; |
| u16 len_on_bd = le16_to_cpu(fp_cqe->len_on_bd); |
| u32 i, frag_len, frag_size, pages; |
| int err; |
| int j; |
| |
| frag_size = le16_to_cpu(fp_cqe->pkt_len) - len_on_bd; |
| pages = SGE_PAGE_ALIGN(frag_size) >> SGE_PAGE_SHIFT; |
| |
| /* This is needed in order to enable forwarding support */ |
| if (frag_size) |
| skb_shinfo(skb)->gso_size = min((u32)SGE_PAGE_SIZE, |
| max(frag_size, (u32)len_on_bd)); |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (pages > min_t(u32, 8, MAX_SKB_FRAGS)*SGE_PAGE_SIZE*PAGES_PER_SGE) { |
| BNX2X_ERR("SGL length is too long: %d. CQE index is %d\n", |
| pages, cqe_idx); |
| BNX2X_ERR("fp_cqe->pkt_len = %d fp_cqe->len_on_bd = %d\n", |
| fp_cqe->pkt_len, len_on_bd); |
| bnx2x_panic(); |
| return -EINVAL; |
| } |
| #endif |
| |
| /* Run through the SGL and compose the fragmented skb */ |
| for (i = 0, j = 0; i < pages; i += PAGES_PER_SGE, j++) { |
| u16 sge_idx = RX_SGE(le16_to_cpu(fp_cqe->sgl[j])); |
| |
| /* FW gives the indices of the SGE as if the ring is an array |
| (meaning that "next" element will consume 2 indices) */ |
| frag_len = min(frag_size, (u32)(SGE_PAGE_SIZE*PAGES_PER_SGE)); |
| rx_pg = &fp->rx_page_ring[sge_idx]; |
| old_rx_pg = *rx_pg; |
| |
| /* If we fail to allocate a substitute page, we simply stop |
| where we are and drop the whole packet */ |
| err = bnx2x_alloc_rx_sge(bp, fp, sge_idx); |
| if (unlikely(err)) { |
| fp->eth_q_stats.rx_skb_alloc_failed++; |
| return err; |
| } |
| |
| /* Unmap the page as we r going to pass it to the stack */ |
| dma_unmap_page(&bp->pdev->dev, |
| dma_unmap_addr(&old_rx_pg, mapping), |
| SGE_PAGE_SIZE*PAGES_PER_SGE, DMA_FROM_DEVICE); |
| |
| /* Add one frag and update the appropriate fields in the skb */ |
| skb_fill_page_desc(skb, j, old_rx_pg.page, 0, frag_len); |
| |
| skb->data_len += frag_len; |
| skb->truesize += frag_len; |
| skb->len += frag_len; |
| |
| frag_size -= frag_len; |
| } |
| |
| return 0; |
| } |
| |
| static void bnx2x_tpa_stop(struct bnx2x *bp, struct bnx2x_fastpath *fp, |
| u16 queue, int pad, int len, union eth_rx_cqe *cqe, |
| u16 cqe_idx) |
| { |
| struct sw_rx_bd *rx_buf = &fp->tpa_pool[queue]; |
| struct sk_buff *skb = rx_buf->skb; |
| /* alloc new skb */ |
| struct sk_buff *new_skb = netdev_alloc_skb(bp->dev, bp->rx_buf_size); |
| |
| /* Unmap skb in the pool anyway, as we are going to change |
| pool entry status to BNX2X_TPA_STOP even if new skb allocation |
| fails. */ |
| dma_unmap_single(&bp->pdev->dev, dma_unmap_addr(rx_buf, mapping), |
| bp->rx_buf_size, DMA_FROM_DEVICE); |
| |
| if (likely(new_skb)) { |
| /* fix ip xsum and give it to the stack */ |
| /* (no need to map the new skb) */ |
| #ifdef BCM_VLAN |
| int is_vlan_cqe = |
| (le16_to_cpu(cqe->fast_path_cqe.pars_flags.flags) & |
| PARSING_FLAGS_VLAN); |
| int is_not_hwaccel_vlan_cqe = |
| (is_vlan_cqe && (!(bp->flags & HW_VLAN_RX_FLAG))); |
| #endif |
| |
| prefetch(skb); |
| prefetch(((char *)(skb)) + 128); |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (pad + len > bp->rx_buf_size) { |
| BNX2X_ERR("skb_put is about to fail... " |
| "pad %d len %d rx_buf_size %d\n", |
| pad, len, bp->rx_buf_size); |
| bnx2x_panic(); |
| return; |
| } |
| #endif |
| |
| skb_reserve(skb, pad); |
| skb_put(skb, len); |
| |
| skb->protocol = eth_type_trans(skb, bp->dev); |
| skb->ip_summed = CHECKSUM_UNNECESSARY; |
| |
| { |
| struct iphdr *iph; |
| |
| iph = (struct iphdr *)skb->data; |
| #ifdef BCM_VLAN |
| /* If there is no Rx VLAN offloading - |
| take VLAN tag into an account */ |
| if (unlikely(is_not_hwaccel_vlan_cqe)) |
| iph = (struct iphdr *)((u8 *)iph + VLAN_HLEN); |
| #endif |
| iph->check = 0; |
| iph->check = ip_fast_csum((u8 *)iph, iph->ihl); |
| } |
| |
| if (!bnx2x_fill_frag_skb(bp, fp, skb, |
| &cqe->fast_path_cqe, cqe_idx)) { |
| #ifdef BCM_VLAN |
| if ((bp->vlgrp != NULL) && is_vlan_cqe && |
| (!is_not_hwaccel_vlan_cqe)) |
| vlan_gro_receive(&fp->napi, bp->vlgrp, |
| le16_to_cpu(cqe->fast_path_cqe. |
| vlan_tag), skb); |
| else |
| #endif |
| napi_gro_receive(&fp->napi, skb); |
| } else { |
| DP(NETIF_MSG_RX_STATUS, "Failed to allocate new pages" |
| " - dropping packet!\n"); |
| dev_kfree_skb(skb); |
| } |
| |
| |
| /* put new skb in bin */ |
| fp->tpa_pool[queue].skb = new_skb; |
| |
| } else { |
| /* else drop the packet and keep the buffer in the bin */ |
| DP(NETIF_MSG_RX_STATUS, |
| "Failed to allocate new skb - dropping packet!\n"); |
| fp->eth_q_stats.rx_skb_alloc_failed++; |
| } |
| |
| fp->tpa_state[queue] = BNX2X_TPA_STOP; |
| } |
| |
| static inline void bnx2x_update_rx_prod(struct bnx2x *bp, |
| struct bnx2x_fastpath *fp, |
| u16 bd_prod, u16 rx_comp_prod, |
| u16 rx_sge_prod) |
| { |
| struct ustorm_eth_rx_producers rx_prods = {0}; |
| int i; |
| |
| /* Update producers */ |
| rx_prods.bd_prod = bd_prod; |
| rx_prods.cqe_prod = rx_comp_prod; |
| rx_prods.sge_prod = rx_sge_prod; |
| |
| /* |
| * Make sure that the BD and SGE data is updated before updating the |
| * producers since FW might read the BD/SGE right after the producer |
| * is updated. |
| * This is only applicable for weak-ordered memory model archs such |
| * as IA-64. The following barrier is also mandatory since FW will |
| * assumes BDs must have buffers. |
| */ |
| wmb(); |
| |
| for (i = 0; i < sizeof(struct ustorm_eth_rx_producers)/4; i++) |
| REG_WR(bp, BAR_USTRORM_INTMEM + |
| USTORM_RX_PRODS_OFFSET(BP_PORT(bp), fp->cl_id) + i*4, |
| ((u32 *)&rx_prods)[i]); |
| |
| mmiowb(); /* keep prod updates ordered */ |
| |
| DP(NETIF_MSG_RX_STATUS, |
| "queue[%d]: wrote bd_prod %u cqe_prod %u sge_prod %u\n", |
| fp->index, bd_prod, rx_comp_prod, rx_sge_prod); |
| } |
| |
| static int bnx2x_rx_int(struct bnx2x_fastpath *fp, int budget) |
| { |
| struct bnx2x *bp = fp->bp; |
| u16 bd_cons, bd_prod, bd_prod_fw, comp_ring_cons; |
| u16 hw_comp_cons, sw_comp_cons, sw_comp_prod; |
| int rx_pkt = 0; |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (unlikely(bp->panic)) |
| return 0; |
| #endif |
| |
| /* CQ "next element" is of the size of the regular element, |
| that's why it's ok here */ |
| hw_comp_cons = le16_to_cpu(*fp->rx_cons_sb); |
| if ((hw_comp_cons & MAX_RCQ_DESC_CNT) == MAX_RCQ_DESC_CNT) |
| hw_comp_cons++; |
| |
| bd_cons = fp->rx_bd_cons; |
| bd_prod = fp->rx_bd_prod; |
| bd_prod_fw = bd_prod; |
| sw_comp_cons = fp->rx_comp_cons; |
| sw_comp_prod = fp->rx_comp_prod; |
| |
| /* Memory barrier necessary as speculative reads of the rx |
| * buffer can be ahead of the index in the status block |
| */ |
| rmb(); |
| |
| DP(NETIF_MSG_RX_STATUS, |
| "queue[%d]: hw_comp_cons %u sw_comp_cons %u\n", |
| fp->index, hw_comp_cons, sw_comp_cons); |
| |
| while (sw_comp_cons != hw_comp_cons) { |
| struct sw_rx_bd *rx_buf = NULL; |
| struct sk_buff *skb; |
| union eth_rx_cqe *cqe; |
| u8 cqe_fp_flags, cqe_fp_status_flags; |
| u16 len, pad; |
| |
| comp_ring_cons = RCQ_BD(sw_comp_cons); |
| bd_prod = RX_BD(bd_prod); |
| bd_cons = RX_BD(bd_cons); |
| |
| /* Prefetch the page containing the BD descriptor |
| at producer's index. It will be needed when new skb is |
| allocated */ |
| prefetch((void *)(PAGE_ALIGN((unsigned long) |
| (&fp->rx_desc_ring[bd_prod])) - |
| PAGE_SIZE + 1)); |
| |
| cqe = &fp->rx_comp_ring[comp_ring_cons]; |
| cqe_fp_flags = cqe->fast_path_cqe.type_error_flags; |
| cqe_fp_status_flags = cqe->fast_path_cqe.status_flags; |
| |
| DP(NETIF_MSG_RX_STATUS, "CQE type %x err %x status %x" |
| " queue %x vlan %x len %u\n", CQE_TYPE(cqe_fp_flags), |
| cqe_fp_flags, cqe->fast_path_cqe.status_flags, |
| le32_to_cpu(cqe->fast_path_cqe.rss_hash_result), |
| le16_to_cpu(cqe->fast_path_cqe.vlan_tag), |
| le16_to_cpu(cqe->fast_path_cqe.pkt_len)); |
| |
| /* is this a slowpath msg? */ |
| if (unlikely(CQE_TYPE(cqe_fp_flags))) { |
| bnx2x_sp_event(fp, cqe); |
| goto next_cqe; |
| |
| /* this is an rx packet */ |
| } else { |
| rx_buf = &fp->rx_buf_ring[bd_cons]; |
| skb = rx_buf->skb; |
| prefetch(skb); |
| len = le16_to_cpu(cqe->fast_path_cqe.pkt_len); |
| pad = cqe->fast_path_cqe.placement_offset; |
| |
| /* If CQE is marked both TPA_START and TPA_END |
| it is a non-TPA CQE */ |
| if ((!fp->disable_tpa) && |
| (TPA_TYPE(cqe_fp_flags) != |
| (TPA_TYPE_START | TPA_TYPE_END))) { |
| u16 queue = cqe->fast_path_cqe.queue_index; |
| |
| if (TPA_TYPE(cqe_fp_flags) == TPA_TYPE_START) { |
| DP(NETIF_MSG_RX_STATUS, |
| "calling tpa_start on queue %d\n", |
| queue); |
| |
| bnx2x_tpa_start(fp, queue, skb, |
| bd_cons, bd_prod); |
| goto next_rx; |
| } |
| |
| if (TPA_TYPE(cqe_fp_flags) == TPA_TYPE_END) { |
| DP(NETIF_MSG_RX_STATUS, |
| "calling tpa_stop on queue %d\n", |
| queue); |
| |
| if (!BNX2X_RX_SUM_FIX(cqe)) |
| BNX2X_ERR("STOP on none TCP " |
| "data\n"); |
| |
| /* This is a size of the linear data |
| on this skb */ |
| len = le16_to_cpu(cqe->fast_path_cqe. |
| len_on_bd); |
| bnx2x_tpa_stop(bp, fp, queue, pad, |
| len, cqe, comp_ring_cons); |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (bp->panic) |
| return 0; |
| #endif |
| |
| bnx2x_update_sge_prod(fp, |
| &cqe->fast_path_cqe); |
| goto next_cqe; |
| } |
| } |
| |
| dma_sync_single_for_device(&bp->pdev->dev, |
| dma_unmap_addr(rx_buf, mapping), |
| pad + RX_COPY_THRESH, |
| DMA_FROM_DEVICE); |
| prefetch(((char *)(skb)) + 128); |
| |
| /* is this an error packet? */ |
| if (unlikely(cqe_fp_flags & ETH_RX_ERROR_FALGS)) { |
| DP(NETIF_MSG_RX_ERR, |
| "ERROR flags %x rx packet %u\n", |
| cqe_fp_flags, sw_comp_cons); |
| fp->eth_q_stats.rx_err_discard_pkt++; |
| goto reuse_rx; |
| } |
| |
| /* Since we don't have a jumbo ring |
| * copy small packets if mtu > 1500 |
| */ |
| if ((bp->dev->mtu > ETH_MAX_PACKET_SIZE) && |
| (len <= RX_COPY_THRESH)) { |
| struct sk_buff *new_skb; |
| |
| new_skb = netdev_alloc_skb(bp->dev, |
| len + pad); |
| if (new_skb == NULL) { |
| DP(NETIF_MSG_RX_ERR, |
| "ERROR packet dropped " |
| "because of alloc failure\n"); |
| fp->eth_q_stats.rx_skb_alloc_failed++; |
| goto reuse_rx; |
| } |
| |
| /* aligned copy */ |
| skb_copy_from_linear_data_offset(skb, pad, |
| new_skb->data + pad, len); |
| skb_reserve(new_skb, pad); |
| skb_put(new_skb, len); |
| |
| bnx2x_reuse_rx_skb(fp, skb, bd_cons, bd_prod); |
| |
| skb = new_skb; |
| |
| } else |
| if (likely(bnx2x_alloc_rx_skb(bp, fp, bd_prod) == 0)) { |
| dma_unmap_single(&bp->pdev->dev, |
| dma_unmap_addr(rx_buf, mapping), |
| bp->rx_buf_size, |
| DMA_FROM_DEVICE); |
| skb_reserve(skb, pad); |
| skb_put(skb, len); |
| |
| } else { |
| DP(NETIF_MSG_RX_ERR, |
| "ERROR packet dropped because " |
| "of alloc failure\n"); |
| fp->eth_q_stats.rx_skb_alloc_failed++; |
| reuse_rx: |
| bnx2x_reuse_rx_skb(fp, skb, bd_cons, bd_prod); |
| goto next_rx; |
| } |
| |
| skb->protocol = eth_type_trans(skb, bp->dev); |
| |
| if ((bp->dev->features & NETIF_F_RXHASH) && |
| (cqe_fp_status_flags & |
| ETH_FAST_PATH_RX_CQE_RSS_HASH_FLG)) |
| skb->rxhash = le32_to_cpu( |
| cqe->fast_path_cqe.rss_hash_result); |
| |
| skb->ip_summed = CHECKSUM_NONE; |
| if (bp->rx_csum) { |
| if (likely(BNX2X_RX_CSUM_OK(cqe))) |
| skb->ip_summed = CHECKSUM_UNNECESSARY; |
| else |
| fp->eth_q_stats.hw_csum_err++; |
| } |
| } |
| |
| skb_record_rx_queue(skb, fp->index); |
| |
| #ifdef BCM_VLAN |
| if ((bp->vlgrp != NULL) && (bp->flags & HW_VLAN_RX_FLAG) && |
| (le16_to_cpu(cqe->fast_path_cqe.pars_flags.flags) & |
| PARSING_FLAGS_VLAN)) |
| vlan_gro_receive(&fp->napi, bp->vlgrp, |
| le16_to_cpu(cqe->fast_path_cqe.vlan_tag), skb); |
| else |
| #endif |
| napi_gro_receive(&fp->napi, skb); |
| |
| |
| next_rx: |
| rx_buf->skb = NULL; |
| |
| bd_cons = NEXT_RX_IDX(bd_cons); |
| bd_prod = NEXT_RX_IDX(bd_prod); |
| bd_prod_fw = NEXT_RX_IDX(bd_prod_fw); |
| rx_pkt++; |
| next_cqe: |
| sw_comp_prod = NEXT_RCQ_IDX(sw_comp_prod); |
| sw_comp_cons = NEXT_RCQ_IDX(sw_comp_cons); |
| |
| if (rx_pkt == budget) |
| break; |
| } /* while */ |
| |
| fp->rx_bd_cons = bd_cons; |
| fp->rx_bd_prod = bd_prod_fw; |
| fp->rx_comp_cons = sw_comp_cons; |
| fp->rx_comp_prod = sw_comp_prod; |
| |
| /* Update producers */ |
| bnx2x_update_rx_prod(bp, fp, bd_prod_fw, sw_comp_prod, |
| fp->rx_sge_prod); |
| |
| fp->rx_pkt += rx_pkt; |
| fp->rx_calls++; |
| |
| return rx_pkt; |
| } |
| |
| static irqreturn_t bnx2x_msix_fp_int(int irq, void *fp_cookie) |
| { |
| struct bnx2x_fastpath *fp = fp_cookie; |
| struct bnx2x *bp = fp->bp; |
| |
| /* Return here if interrupt is disabled */ |
| if (unlikely(atomic_read(&bp->intr_sem) != 0)) { |
| DP(NETIF_MSG_INTR, "called but intr_sem not 0, returning\n"); |
| return IRQ_HANDLED; |
| } |
| |
| DP(BNX2X_MSG_FP, "got an MSI-X interrupt on IDX:SB [%d:%d]\n", |
| fp->index, fp->sb_id); |
| bnx2x_ack_sb(bp, fp->sb_id, USTORM_ID, 0, IGU_INT_DISABLE, 0); |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (unlikely(bp->panic)) |
| return IRQ_HANDLED; |
| #endif |
| |
| /* Handle Rx and Tx according to MSI-X vector */ |
| prefetch(fp->rx_cons_sb); |
| prefetch(fp->tx_cons_sb); |
| prefetch(&fp->status_blk->u_status_block.status_block_index); |
| prefetch(&fp->status_blk->c_status_block.status_block_index); |
| napi_schedule(&bnx2x_fp(bp, fp->index, napi)); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static 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; |
| |
| /* 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); |
| |
| /* Return here if interrupt is disabled */ |
| if (unlikely(atomic_read(&bp->intr_sem) != 0)) { |
| DP(NETIF_MSG_INTR, "called but intr_sem not 0, returning\n"); |
| return IRQ_HANDLED; |
| } |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (unlikely(bp->panic)) |
| return IRQ_HANDLED; |
| #endif |
| |
| for (i = 0; i < BNX2X_NUM_QUEUES(bp); i++) { |
| struct bnx2x_fastpath *fp = &bp->fp[i]; |
| |
| mask = 0x2 << fp->sb_id; |
| if (status & mask) { |
| /* Handle Rx and Tx according to SB id */ |
| prefetch(fp->rx_cons_sb); |
| prefetch(&fp->status_blk->u_status_block. |
| status_block_index); |
| prefetch(fp->tx_cons_sb); |
| prefetch(&fp->status_blk->c_status_block. |
| status_block_index); |
| napi_schedule(&bnx2x_fp(bp, fp->index, napi)); |
| status &= ~mask; |
| } |
| } |
| |
| #ifdef BCM_CNIC |
| mask = 0x2 << CNIC_SB_ID(bp); |
| if (status & (mask | 0x1)) { |
| struct cnic_ops *c_ops = NULL; |
| |
| rcu_read_lock(); |
| c_ops = rcu_dereference(bp->cnic_ops); |
| if (c_ops) |
| c_ops->cnic_handler(bp->cnic_data, NULL); |
| rcu_read_unlock(); |
| |
| status &= ~mask; |
| } |
| #endif |
| |
| if (unlikely(status & 0x1)) { |
| queue_delayed_work(bnx2x_wq, &bp->sp_task, 0); |
| |
| 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; |
| } |
| |
| /* end of fast path */ |
| |
| static void bnx2x_stats_handle(struct bnx2x *bp, enum bnx2x_stats_event event); |
| |
| /* Link */ |
| |
| /* |
| * General service functions |
| */ |
| |
| static 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) { |
| DP(NETIF_MSG_HW, |
| "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) { |
| DP(NETIF_MSG_HW, "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; |
| |
| msleep(5); |
| } |
| DP(NETIF_MSG_HW, "Timeout\n"); |
| return -EAGAIN; |
| } |
| |
| static 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; |
| |
| DP(NETIF_MSG_HW, "Releasing 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, |
| "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)) { |
| DP(NETIF_MSG_HW, "lock_status 0x%x resource_bit 0x%x\n", |
| lock_status, resource_bit); |
| return -EFAULT; |
| } |
| |
| REG_WR(bp, hw_lock_control_reg, resource_bit); |
| return 0; |
| } |
| |
| /* HW Lock for shared dual port PHYs */ |
| static void bnx2x_acquire_phy_lock(struct bnx2x *bp) |
| { |
| mutex_lock(&bp->port.phy_mutex); |
| |
| if (bp->port.need_hw_lock) |
| bnx2x_acquire_hw_lock(bp, HW_LOCK_RESOURCE_MDIO); |
| } |
| |
| static void bnx2x_release_phy_lock(struct bnx2x *bp) |
| { |
| if (bp->port.need_hw_lock) |
| bnx2x_release_hw_lock(bp, HW_LOCK_RESOURCE_MDIO); |
| |
| mutex_unlock(&bp->port.phy_mutex); |
| } |
| |
| 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; |
| |
| DP(NETIF_MSG_LINK, "pin %d value 0x%x\n", gpio_num, value); |
| |
| 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_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_num, u32 mode) |
| { |
| u32 spio_mask = (1 << spio_num); |
| u32 spio_reg; |
| |
| if ((spio_num < MISC_REGISTERS_SPIO_4) || |
| (spio_num > MISC_REGISTERS_SPIO_7)) { |
| BNX2X_ERR("Invalid SPIO %d\n", spio_num); |
| 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_REGISTERS_SPIO_FLOAT); |
| |
| switch (mode) { |
| case MISC_REGISTERS_SPIO_OUTPUT_LOW: |
| DP(NETIF_MSG_LINK, "Set SPIO %d -> output low\n", spio_num); |
| /* clear FLOAT and set CLR */ |
| spio_reg &= ~(spio_mask << MISC_REGISTERS_SPIO_FLOAT_POS); |
| spio_reg |= (spio_mask << MISC_REGISTERS_SPIO_CLR_POS); |
| break; |
| |
| case MISC_REGISTERS_SPIO_OUTPUT_HIGH: |
| DP(NETIF_MSG_LINK, "Set SPIO %d -> output high\n", spio_num); |
| /* clear FLOAT and set SET */ |
| spio_reg &= ~(spio_mask << MISC_REGISTERS_SPIO_FLOAT_POS); |
| spio_reg |= (spio_mask << MISC_REGISTERS_SPIO_SET_POS); |
| break; |
| |
| case MISC_REGISTERS_SPIO_INPUT_HI_Z: |
| DP(NETIF_MSG_LINK, "Set SPIO %d -> input\n", spio_num); |
| /* set FLOAT */ |
| spio_reg |= (spio_mask << MISC_REGISTERS_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; |
| } |
| |
| static void bnx2x_calc_fc_adv(struct bnx2x *bp) |
| { |
| switch (bp->link_vars.ieee_fc & |
| MDIO_COMBO_IEEE0_AUTO_NEG_ADV_PAUSE_MASK) { |
| case MDIO_COMBO_IEEE0_AUTO_NEG_ADV_PAUSE_NONE: |
| bp->port.advertising &= ~(ADVERTISED_Asym_Pause | |
| ADVERTISED_Pause); |
| break; |
| |
| case MDIO_COMBO_IEEE0_AUTO_NEG_ADV_PAUSE_BOTH: |
| bp->port.advertising |= (ADVERTISED_Asym_Pause | |
| ADVERTISED_Pause); |
| break; |
| |
| case MDIO_COMBO_IEEE0_AUTO_NEG_ADV_PAUSE_ASYMMETRIC: |
| bp->port.advertising |= ADVERTISED_Asym_Pause; |
| break; |
| |
| default: |
| bp->port.advertising &= ~(ADVERTISED_Asym_Pause | |
| ADVERTISED_Pause); |
| break; |
| } |
| } |
| |
| static void bnx2x_link_report(struct bnx2x *bp) |
| { |
| if (bp->flags & MF_FUNC_DIS) { |
| netif_carrier_off(bp->dev); |
| netdev_err(bp->dev, "NIC Link is Down\n"); |
| return; |
| } |
| |
| if (bp->link_vars.link_up) { |
| u16 line_speed; |
| |
| if (bp->state == BNX2X_STATE_OPEN) |
| netif_carrier_on(bp->dev); |
| netdev_info(bp->dev, "NIC Link is Up, "); |
| |
| line_speed = bp->link_vars.line_speed; |
| if (IS_E1HMF(bp)) { |
| u16 vn_max_rate; |
| |
| vn_max_rate = |
| ((bp->mf_config & FUNC_MF_CFG_MAX_BW_MASK) >> |
| FUNC_MF_CFG_MAX_BW_SHIFT) * 100; |
| if (vn_max_rate < line_speed) |
| line_speed = vn_max_rate; |
| } |
| pr_cont("%d Mbps ", line_speed); |
| |
| if (bp->link_vars.duplex == DUPLEX_FULL) |
| pr_cont("full duplex"); |
| else |
| pr_cont("half duplex"); |
| |
| if (bp->link_vars.flow_ctrl != BNX2X_FLOW_CTRL_NONE) { |
| if (bp->link_vars.flow_ctrl & BNX2X_FLOW_CTRL_RX) { |
| pr_cont(", receive "); |
| if (bp->link_vars.flow_ctrl & |
| BNX2X_FLOW_CTRL_TX) |
| pr_cont("& transmit "); |
| } else { |
| pr_cont(", transmit "); |
| } |
| pr_cont("flow control ON"); |
| } |
| pr_cont("\n"); |
| |
| } else { /* link_down */ |
| netif_carrier_off(bp->dev); |
| netdev_err(bp->dev, "NIC Link is Down\n"); |
| } |
| } |
| |
| static u8 bnx2x_initial_phy_init(struct bnx2x *bp, int load_mode) |
| { |
| if (!BP_NOMCP(bp)) { |
| u8 rc; |
| |
| /* Initialize link parameters structure variables */ |
| /* It is recommended to turn off RX FC for jumbo frames |
| for better performance */ |
| if (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; |
| |
| bnx2x_acquire_phy_lock(bp); |
| |
| if (load_mode == LOAD_DIAG) |
| bp->link_params.loopback_mode = LOOPBACK_XGXS_10; |
| |
| rc = bnx2x_phy_init(&bp->link_params, &bp->link_vars); |
| |
| bnx2x_release_phy_lock(bp); |
| |
| bnx2x_calc_fc_adv(bp); |
| |
| if (CHIP_REV_IS_SLOW(bp) && bp->link_vars.link_up) { |
| bnx2x_stats_handle(bp, STATS_EVENT_LINK_UP); |
| bnx2x_link_report(bp); |
| } |
| |
| return rc; |
| } |
| BNX2X_ERR("Bootcode is missing - can not initialize link\n"); |
| return -EINVAL; |
| } |
| |
| static 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_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_link_reset(&bp->link_params, &bp->link_vars, 1); |
| bnx2x_release_phy_lock(bp); |
| } else |
| BNX2X_ERR("Bootcode is missing - can not reset link\n"); |
| } |
| |
| static u8 bnx2x_link_test(struct bnx2x *bp) |
| { |
| u8 rc = 0; |
| |
| if (!BP_NOMCP(bp)) { |
| bnx2x_acquire_phy_lock(bp); |
| rc = bnx2x_test_link(&bp->link_params, &bp->link_vars); |
| bnx2x_release_phy_lock(bp); |
| } else |
| BNX2X_ERR("Bootcode is missing - can not test link\n"); |
| |
| return rc; |
| } |
| |
| static void bnx2x_init_port_minmax(struct bnx2x *bp) |
| { |
| u32 r_param = bp->link_vars.line_speed / 8; |
| u32 fair_periodic_timeout_usec; |
| u32 t_fair; |
| |
| memset(&(bp->cmng.rs_vars), 0, |
| sizeof(struct rate_shaping_vars_per_port)); |
| memset(&(bp->cmng.fair_vars), 0, sizeof(struct fairness_vars_per_port)); |
| |
| /* 100 usec in SDM ticks = 25 since each tick is 4 usec */ |
| bp->cmng.rs_vars.rs_periodic_timeout = RS_PERIODIC_TIMEOUT_USEC / 4; |
| |
| /* this is the threshold below which no timer arming will occur |
| 1.25 coefficient is for the threshold to be a little bigger |
| than the real time, to compensate for timer in-accuracy */ |
| bp->cmng.rs_vars.rs_threshold = |
| (RS_PERIODIC_TIMEOUT_USEC * r_param * 5) / 4; |
| |
| /* resolution of fairness timer */ |
| fair_periodic_timeout_usec = QM_ARB_BYTES / r_param; |
| /* for 10G it is 1000usec. for 1G it is 10000usec. */ |
| t_fair = T_FAIR_COEF / bp->link_vars.line_speed; |
| |
| /* this is the threshold below which we won't arm the timer anymore */ |
| bp->cmng.fair_vars.fair_threshold = QM_ARB_BYTES; |
| |
| /* we multiply by 1e3/8 to get bytes/msec. |
| We don't want the credits to pass a credit |
| of the t_fair*FAIR_MEM (algorithm resolution) */ |
| bp->cmng.fair_vars.upper_bound = r_param * t_fair * FAIR_MEM; |
| /* since each tick is 4 usec */ |
| bp->cmng.fair_vars.fairness_timeout = fair_periodic_timeout_usec / 4; |
| } |
| |
| /* 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 fainess 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_weight_sum(struct bnx2x *bp) |
| { |
| int all_zero = 1; |
| int port = BP_PORT(bp); |
| int vn; |
| |
| bp->vn_weight_sum = 0; |
| for (vn = VN_0; vn < E1HVN_MAX; vn++) { |
| int func = 2*vn + port; |
| u32 vn_cfg = SHMEM_RD(bp, mf_cfg.func_mf_config[func].config); |
| 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) |
| continue; |
| |
| /* If min rate is zero - set it to 1 */ |
| if (!vn_min_rate) |
| vn_min_rate = DEF_MIN_RATE; |
| else |
| all_zero = 0; |
| |
| bp->vn_weight_sum += vn_min_rate; |
| } |
| |
| /* ... only if all min rates are zeros - disable fairness */ |
| if (all_zero) { |
| bp->cmng.flags.cmng_enables &= |
| ~CMNG_FLAGS_PER_PORT_FAIRNESS_VN; |
| DP(NETIF_MSG_IFUP, "All MIN values are zeroes" |
| " fairness will be disabled\n"); |
| } else |
| bp->cmng.flags.cmng_enables |= |
| CMNG_FLAGS_PER_PORT_FAIRNESS_VN; |
| } |
| |
| static void bnx2x_init_vn_minmax(struct bnx2x *bp, int func) |
| { |
| struct rate_shaping_vars_per_vn m_rs_vn; |
| struct fairness_vars_per_vn m_fair_vn; |
| u32 vn_cfg = SHMEM_RD(bp, mf_cfg.func_mf_config[func].config); |
| u16 vn_min_rate, vn_max_rate; |
| int i; |
| |
| /* If function is hidden - set min and max to zeroes */ |
| if (vn_cfg & FUNC_MF_CFG_FUNC_HIDE) { |
| vn_min_rate = 0; |
| vn_max_rate = 0; |
| |
| } else { |
| vn_min_rate = ((vn_cfg & FUNC_MF_CFG_MIN_BW_MASK) >> |
| FUNC_MF_CFG_MIN_BW_SHIFT) * 100; |
| /* If min rate is zero - set it to 1 */ |
| if (!vn_min_rate) |
| vn_min_rate = DEF_MIN_RATE; |
| vn_max_rate = ((vn_cfg & FUNC_MF_CFG_MAX_BW_MASK) >> |
| FUNC_MF_CFG_MAX_BW_SHIFT) * 100; |
| } |
| DP(NETIF_MSG_IFUP, |
| "func %d: vn_min_rate %d vn_max_rate %d vn_weight_sum %d\n", |
| func, vn_min_rate, vn_max_rate, bp->vn_weight_sum); |
| |
| memset(&m_rs_vn, 0, sizeof(struct rate_shaping_vars_per_vn)); |
| memset(&m_fair_vn, 0, sizeof(struct fairness_vars_per_vn)); |
| |
| /* global vn counter - maximal Mbps for this vn */ |
| m_rs_vn.vn_counter.rate = vn_max_rate; |
| |
| /* quota - number of bytes transmitted in this period */ |
| m_rs_vn.vn_counter.quota = |
| (vn_max_rate * RS_PERIODIC_TIMEOUT_USEC) / 8; |
| |
| if (bp->vn_weight_sum) { |
| /* credit for each period of the fairness algorithm: |
| number of bytes in T_FAIR (the vn share the port rate). |
| vn_weight_sum should not be larger than 10000, thus |
| T_FAIR_COEF / (8 * vn_weight_sum) will always be greater |
| than zero */ |
| m_fair_vn.vn_credit_delta = |
| max_t(u32, (vn_min_rate * (T_FAIR_COEF / |
| (8 * bp->vn_weight_sum))), |
| (bp->cmng.fair_vars.fair_threshold * 2)); |
| DP(NETIF_MSG_IFUP, "m_fair_vn.vn_credit_delta %d\n", |
| m_fair_vn.vn_credit_delta); |
| } |
| |
| /* Store it to internal memory */ |
| for (i = 0; i < sizeof(struct rate_shaping_vars_per_vn)/4; i++) |
| REG_WR(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_RATE_SHAPING_PER_VN_VARS_OFFSET(func) + i * 4, |
| ((u32 *)(&m_rs_vn))[i]); |
| |
| for (i = 0; i < sizeof(struct fairness_vars_per_vn)/4; i++) |
| REG_WR(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_FAIRNESS_PER_VN_VARS_OFFSET(func) + i * 4, |
| ((u32 *)(&m_fair_vn))[i]); |
| } |
| |
| |
| /* This function is called upon link interrupt */ |
| static void bnx2x_link_attn(struct bnx2x *bp) |
| { |
| u32 prev_link_status = bp->link_vars.link_status; |
| /* 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); |
| |
| if (bp->link_vars.link_up) { |
| |
| /* dropless flow control */ |
| if (CHIP_IS_E1H(bp) && bp->dropless_fc) { |
| int port = BP_PORT(bp); |
| u32 pause_enabled = 0; |
| |
| if (bp->link_vars.flow_ctrl & BNX2X_FLOW_CTRL_TX) |
| pause_enabled = 1; |
| |
| REG_WR(bp, BAR_USTRORM_INTMEM + |
| USTORM_ETH_PAUSE_ENABLED_OFFSET(port), |
| pause_enabled); |
| } |
| |
| if (bp->link_vars.mac_type == MAC_TYPE_BMAC) { |
| struct host_port_stats *pstats; |
| |
| pstats = bnx2x_sp(bp, port_stats); |
| /* reset old bmac 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); |
| } |
| |
| /* indicate link status only if link status actually changed */ |
| if (prev_link_status != bp->link_vars.link_status) |
| bnx2x_link_report(bp); |
| |
| if (IS_E1HMF(bp)) { |
| int port = BP_PORT(bp); |
| int func; |
| int vn; |
| |
| /* Set the attention towards other drivers on the same port */ |
| for (vn = VN_0; vn < E1HVN_MAX; vn++) { |
| if (vn == BP_E1HVN(bp)) |
| continue; |
| |
| func = ((vn << 1) | port); |
| REG_WR(bp, MISC_REG_AEU_GENERAL_ATTN_0 + |
| (LINK_SYNC_ATTENTION_BIT_FUNC_0 + func)*4, 1); |
| } |
| |
| if (bp->link_vars.link_up) { |
| int i; |
| |
| /* Init rate shaping and fairness contexts */ |
| bnx2x_init_port_minmax(bp); |
| |
| for (vn = VN_0; vn < E1HVN_MAX; vn++) |
| bnx2x_init_vn_minmax(bp, 2*vn + port); |
| |
| /* Store it to internal memory */ |
| for (i = 0; |
| i < sizeof(struct cmng_struct_per_port) / 4; i++) |
| REG_WR(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_CMNG_PER_PORT_VARS_OFFSET(port) + i*4, |
| ((u32 *)(&bp->cmng))[i]); |
| } |
| } |
| } |
| |
| static void bnx2x__link_status_update(struct bnx2x *bp) |
| { |
| if ((bp->state != BNX2X_STATE_OPEN) || (bp->flags & MF_FUNC_DIS)) |
| return; |
| |
| 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); |
| |
| bnx2x_calc_vn_weight_sum(bp); |
| |
| /* indicate link status */ |
| bnx2x_link_report(bp); |
| } |
| |
| static void bnx2x_pmf_update(struct bnx2x *bp) |
| { |
| int port = BP_PORT(bp); |
| u32 val; |
| |
| bp->port.pmf = 1; |
| DP(NETIF_MSG_LINK, "pmf %d\n", bp->port.pmf); |
| |
| /* enable nig attention */ |
| val = (0xff0f | (1 << (BP_E1HVN(bp) + 4))); |
| REG_WR(bp, HC_REG_TRAILING_EDGE_0 + port*8, val); |
| REG_WR(bp, HC_REG_LEADING_EDGE_0 + port*8, 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) |
| { |
| int func = BP_FUNC(bp); |
| u32 seq = ++bp->fw_seq; |
| u32 rc = 0; |
| u32 cnt = 1; |
| u8 delay = CHIP_REV_IS_SLOW(bp) ? 100 : 10; |
| |
| mutex_lock(&bp->fw_mb_mutex); |
| SHMEM_WR(bp, func_mb[func].drv_mb_header, (command | seq)); |
| DP(BNX2X_MSG_MCP, "wrote command (%x) to FW MB\n", (command | seq)); |
| |
| do { |
| /* let the FW do it's magic ... */ |
| msleep(delay); |
| |
| rc = SHMEM_RD(bp, func_mb[func].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 bnx2x_set_eth_mac_addr_e1h(struct bnx2x *bp, int set); |
| static void bnx2x_set_rx_mode(struct net_device *dev); |
| |
| static void bnx2x_e1h_disable(struct bnx2x *bp) |
| { |
| int port = BP_PORT(bp); |
| |
| netif_tx_disable(bp->dev); |
| |
| REG_WR(bp, NIG_REG_LLH0_FUNC_EN + port*8, 0); |
| |
| netif_carrier_off(bp->dev); |
| } |
| |
| static void bnx2x_e1h_enable(struct bnx2x *bp) |
| { |
| int port = BP_PORT(bp); |
| |
| REG_WR(bp, NIG_REG_LLH0_FUNC_EN + port*8, 1); |
| |
| /* Tx queue should be only reenabled */ |
| netif_tx_wake_all_queues(bp->dev); |
| |
| /* |
| * Should not call netif_carrier_on since it will be called if the link |
| * is up when checking for link state |
| */ |
| } |
| |
| static void bnx2x_update_min_max(struct bnx2x *bp) |
| { |
| int port = BP_PORT(bp); |
| int vn, i; |
| |
| /* Init rate shaping and fairness contexts */ |
| bnx2x_init_port_minmax(bp); |
| |
| bnx2x_calc_vn_weight_sum(bp); |
| |
| for (vn = VN_0; vn < E1HVN_MAX; vn++) |
| bnx2x_init_vn_minmax(bp, 2*vn + port); |
| |
| if (bp->port.pmf) { |
| int func; |
| |
| /* Set the attention towards other drivers on the same port */ |
| for (vn = VN_0; vn < E1HVN_MAX; vn++) { |
| if (vn == BP_E1HVN(bp)) |
| continue; |
| |
| func = ((vn << 1) | port); |
| REG_WR(bp, MISC_REG_AEU_GENERAL_ATTN_0 + |
| (LINK_SYNC_ATTENTION_BIT_FUNC_0 + func)*4, 1); |
| } |
| |
| /* Store it to internal memory */ |
| for (i = 0; i < sizeof(struct cmng_struct_per_port) / 4; i++) |
| REG_WR(bp, BAR_XSTRORM_INTMEM + |
| XSTORM_CMNG_PER_PORT_VARS_OFFSET(port) + i*4, |
| ((u32 *)(&bp->cmng))[i]); |
| } |
| } |
| |
| static void bnx2x_dcc_event(struct bnx2x *bp, u32 dcc_event) |
| { |
| DP(BNX2X_MSG_MCP, "dcc_event 0x%x\n", dcc_event); |
| |
| if (dcc_event & DRV_STATUS_DCC_DISABLE_ENABLE_PF) { |
| |
| /* |
| * This is the only place besides the function initialization |
| * where the bp->flags can change so it is done without any |
| * locks |
| */ |
| if (bp->mf_config & FUNC_MF_CFG_FUNC_DISABLED) { |
| DP(NETIF_MSG_IFDOWN, "mf_cfg function disabled\n"); |
| bp->flags |= MF_FUNC_DIS; |
| |
| bnx2x_e1h_disable(bp); |
| } else { |
| DP(NETIF_MSG_IFUP, "mf_cfg function enabled\n"); |
| bp->flags &= ~MF_FUNC_DIS; |
| |
| bnx2x_e1h_enable(bp); |
| } |
| dcc_event &= ~DRV_STATUS_DCC_DISABLE_ENABLE_PF; |
| } |
| if (dcc_event & DRV_STATUS_DCC_BANDWIDTH_ALLOCATION) { |
| |
| bnx2x_update_min_max(bp); |
| dcc_event &= ~DRV_STATUS_DCC_BANDWIDTH_ALLOCATION; |
| } |
| |
| /* Report results to MCP */ |
| if (dcc_event) |
| bnx2x_fw_command(bp, DRV_MSG_CODE_DCC_FAILURE); |
| else |
| bnx2x_fw_command(bp, DRV_MSG_CODE_DCC_OK); |
| } |
| |
| /* must be called under the spq lock */ |
| static inline struct eth_spe *bnx2x_sp_get_next(struct bnx2x *bp) |
| { |
| struct eth_spe *next_spe = bp->spq_prod_bd; |
| |
| if (bp->spq_prod_bd == bp->spq_last_bd) { |
| bp->spq_prod_bd = bp->spq; |
| bp->spq_prod_idx = 0; |
| DP(NETIF_MSG_TIMER, "end of spq\n"); |
| } else { |
| bp->spq_prod_bd++; |
| bp->spq_prod_idx++; |
| } |
| return next_spe; |
| } |
| |
| /* must be called under the spq lock */ |
| static inline void bnx2x_sp_prod_update(struct bnx2x *bp) |
| { |
| int func = BP_FUNC(bp); |
| |
| /* Make sure that BD data is updated before writing the producer */ |
| wmb(); |
| |
| REG_WR(bp, BAR_XSTRORM_INTMEM + XSTORM_SPQ_PROD_OFFSET(func), |
| bp->spq_prod_idx); |
| mmiowb(); |
| } |
| |
| /* the slow path queue is odd since completions arrive on the fastpath ring */ |
| static int bnx2x_sp_post(struct bnx2x *bp, int command, int cid, |
| u32 data_hi, u32 data_lo, int common) |
| { |
| struct eth_spe *spe; |
| |
| #ifdef BNX2X_STOP_ON_ERROR |
| if (unlikely(bp->panic)) |
| return -EIO; |
| #endif |
| |
| spin_lock_bh(&bp->spq_lock); |
| |
| if (!bp->spq_left) { |
| BNX2X_ERR("BUG! SPQ ring full!\n"); |
| spin_unlock_bh(&bp->spq_lock); |
| bnx2x_panic(); |
| return -EBUSY; |
| } |
| |
| spe = bnx2x_sp_get_next(bp); |
| |
| /* CID needs port number to be encoded int it */ |
| spe->hdr.conn_and_cmd_data = |
| cpu_to_le32((command << SPE_HDR_CMD_ID_SHIFT) | |
| HW_CID(bp, cid)); |
| spe->hdr.type = cpu_to_le16(ETH_CONNECTION_TYPE); |
| if (common) |
| spe->hdr.type |= |
| cpu_to_le16((1 << SPE_HDR_COMMON_RAMROD_SHIFT)); |
| |
| spe->data.mac_config_addr.hi = cpu_to_le32(data_hi); |
| spe->data.mac_config_addr.lo = cpu_to_le32(data_lo); |
| |
| bp->spq_left--; |
| |
| DP(BNX2X_MSG_SP/*NETIF_MSG_TIMER*/, |
| "SPQE[%x] (%x:%x) command %d hw_cid %x data (%x:%x) left %x\n", |
| bp->spq_prod_idx, (u32)U64_HI(bp->spq_mapping), |
| (u32)(U64_LO(bp->spq_mapping) + |
| (void *)bp->spq_prod_bd - (void *)bp->spq), command, |
| HW_CID(bp, cid), data_hi, data_lo, bp->spq_left); |
| |
| bnx2x_sp_prod_update(bp); |
| spin_unlock_bh(&bp->spq_lock); |
| return 0; |
| } |
| |
| /* acquire split MCP access lock register */ |
| static int bnx2x_acquire_alr(struct bnx2x *bp) |
| { |
| u32 j, val; |
| int rc = 0; |
| |
| might_sleep(); |
| for (j = 0; j < 1000; j++) { |
| val = (1UL << 31); |
| REG_WR(bp, GRCBASE_MCP + 0x9c, val); |
| val = REG_RD(bp, GRCBASE_MCP + 0x9c); |
| if (val & (1L << 31)) |
| break; |
| |
| msleep(5); |
| } |
| if (!(val & (1L << 31))) { |
| BNX2X_ERR("Cannot acquire MCP access lock register\n"); |
| rc = -EBUSY; |
| } |
| |
| return rc; |
| } |
| |
| /* release split MCP access lock register */ |
| static void bnx2x_release_alr(struct bnx2x *bp) |
| { |
| REG_WR(bp, GRCBASE_MCP + 0x9c, 0); |
| } |
| |
| static inline u16 bnx2x_update_dsb_idx(struct bnx2x *bp) |
| { |
| struct host_def_status_block *def_sb = bp->def_status_blk; |
| u16 rc = 0; |
| |
| barrier(); /* status block is written to by the chip */ |
| if (bp->def_att_idx != def_sb->atten_status_block.attn_bits_index) { |
| bp->def_att_idx = def_sb->atten_status_block.attn_bits_index; |
| rc |= 1; |
| } |
| if (bp->def_c_idx != def_sb->c_def_status_block.status_block_index) { |
| bp->def_c_idx = def_sb->c_def_status_block.status_block_index; |
| rc |= 2; |
| } |
| if (bp->def_u_idx != def_sb->u_def_status_block.status_block_index) { |
| bp->def_u_idx = def_sb->u_def_status_block.status_block_index; |
| rc |= 4; |
| } |
| if (bp->def_x_idx != def_sb->x_def_status_block.status_block_index) { |
| bp->def_x_idx = def_sb->x_def_status_block.status_block_index; |
| rc |= 8; |
| } |
| if (bp->def_t_idx != def_sb->t_def_status_block.status_block_index) { |
| bp->def_t_idx = def_sb->t_def_status_block.status_block_index; |
| rc |= 16; |
| } |
| return rc; |
| } |
| |
| /* |
| * slow path service functions |
| */ |
| |
| static void bnx2x_attn_int_asserted(struct bnx2x *bp, u32 asserted) |
| { |
| int port = BP_PORT(bp); |
| u32 hc_addr = (HC_REG_COMMAND_REG + port*32 + |
| COMMAND_REG_ATTN_BITS_SET); |
| u32 aeu_addr = port ? MISC_REG_AEU_MASK_ATTN_FUNC_1 : |
| MISC_REG_AEU_MASK_ATTN_FUNC_0; |
| u32 nig_int_mask_addr = port ? NIG_REG_MASK_INTERRUPT_PORT1 : |
| NIG_REG_MASK_INTERRUPT_PORT0; |
| u32 aeu_mask; |
| u32 nig_mask = 0; |
| |
| if (bp->attn_state & asserted) |
| BNX2X_ERR("IGU ERROR\n"); |
| |
| bnx2x_acquire_hw_lock(bp, HW_LOCK_RESOURCE_PORT0_ATT_MASK + port); |
| aeu_mask = REG_RD(bp, aeu_addr); |
| |
| DP(NETIF_MSG_HW, "aeu_mask %x newly asserted %x\n", |
| aeu_mask, asserted); |
| aeu_mask &= ~(asserted & 0x3ff); |
| DP(NETIF_MSG_HW, "new mask %x\n", aeu_mask); |
| |
| REG_WR(bp, aeu_addr, aeu_mask); |
| bnx2x_release_hw_lock(bp, HW_LOCK_RESOURCE_PORT0_ATT_MASK + port); |
| |
| DP(NETIF_MSG_HW, "attn_state %x\n", bp->attn_state); |
| bp->attn_state |= asserted; |
| DP(NETIF_MSG_HW, "new state %x\n", bp->attn_state); |
| |
| if (asserted & ATTN_HARD_WIRED_MASK) { |
| if (asserted & ATTN_NIG_FOR_FUNC) { |
| |
| bnx2x_acquire_phy_lock(bp); |
| |
| /* save nig interrupt mask */ |
| nig_mask = REG_RD(bp, nig_int_mask_addr); |
| REG_WR(bp, nig_int_mask_addr, 0); |
| |
| bnx2x_link_attn(bp); |
| |
| /* handle unicore attn? */ |
| } |
| if (asserted & ATTN_SW_TIMER_4_FUNC) |
| DP(NETIF_MSG_HW, "ATTN_SW_TIMER_4_FUNC!\n"); |
| |
| if (asserted & GPIO_2_FUNC) |
| DP(NETIF_MSG_HW, "GPIO_2_FUNC!\n"); |
| |
| if (asserted & GPIO_3_FUNC) |
| DP(NETIF_MSG_HW, "GPIO_3_FUNC!\n"); |
| |
| if (asserted & GPIO_4_FUNC) |
| DP(NETIF_MSG_HW, "GPIO_4_FUNC!\n"); |
| |
| if (port == 0) { |
| if (asserted & ATTN_GENERAL_ATTN_1) { |
| DP(NETIF_MSG_HW, "ATTN_GENERAL_ATTN_1!\n"); |
| REG_WR(bp, MISC_REG_AEU_GENERAL_ATTN_1, 0x0); |
| } |
| if (asserted & ATTN_GENERAL_ATTN_2) { |
| DP(NETIF_MSG_HW, "ATTN_GENERAL_ATTN_2!\n"); |
| REG_WR(bp, MISC_REG_AEU_GENERAL_ATTN_2, 0x0); |
| } |
| if (asserted & ATTN_GENERAL_ATTN_3) { |
| DP(NETIF_MSG_HW, "ATTN_GENERAL_ATTN_3!\n"); |
| REG_WR(bp, MISC_REG_AEU_GENERAL_ATTN_3, 0x0); |
| } |
| } else { |
| if (asserted & ATTN_GENERAL_ATTN_4) { |
| DP(NETIF_MSG_HW, "ATTN_GENERAL_ATTN_4!\n"); |
| REG_WR(bp, MISC_REG_AEU_GENERAL_ATTN_4, 0x0); |
| } |
| if (asserted & ATTN_GENERAL_ATTN_5) { |
| DP(NETIF_MSG_HW, "ATTN_GENERAL_ATTN_5!\n"); |
| REG_WR(bp, MISC_REG_AEU_GENERAL_ATTN_5, 0x0); |
| } |
| if (asserted & ATTN_GENERAL_ATTN_6) { |
| DP(NETIF_MSG_HW, "ATTN_GENERAL_ATTN_6!\n"); |
| REG_WR(bp, MISC_REG_AEU_GENERAL_ATTN_6, 0x0); |
| } |
| } |
| |
| } /* if hardwired */ |
| |
| DP(NETIF_MSG_HW, "about to mask 0x%08x at HC addr 0x%x\n", |
| asserted, hc_addr); |
| REG_WR(bp, hc_addr, asserted); |
| |
| /* now set back the mask */ |
| if (asserted & ATTN_NIG_FOR_FUNC) { |
| REG_WR(bp, nig_int_mask_addr, nig_mask); |
| bnx2x_release_phy_lock(bp); |
| } |
| } |
| |
| static inline void bnx2x_fan_failure(struct bnx2x *bp) |
| { |
| int port = BP_PORT(bp); |
| |
| /* mark the failure */ |
| bp->link_params.ext_phy_config &= ~PORT_HW_CFG_XGXS_EXT_PHY_TYPE_MASK; |
| bp->link_params.ext_phy_config |= PORT_HW_CFG_XGXS_EXT_PHY_TYPE_FAILURE; |
| SHMEM_WR(bp, dev_info.port_hw_config[port].external_phy_config, |
| bp->link_params.ext_phy_config); |
| |
| /* log the failure */ |
| netdev_err(bp->dev, "Fan Failure on Network Controller has caused" |
| " the driver to shutdown the card to prevent permanent" |
| " damage. Please contact OEM Support for assistance\n"); |
| } |
| |
| static inline void bnx2x_attn_int_deasserted0(struct bnx2x *bp, u32 attn) |
| { |
| int port = BP_PORT(bp); |
| int reg_offset; |
| u32 val, swap_val, swap_override; |
| |
| reg_offset = (port ? MISC_REG_AEU_ENABLE1_FUNC_1_OUT_0 : |
| MISC_REG_AEU_ENABLE1_FUNC_0_OUT_0); |
| |
| if (attn & AEU_INPUTS_ATTN_BITS_SPIO5) { |
| |
| val = REG_RD(bp, reg_offset); |
| val &= ~AEU_INPUTS_ATTN_BITS_SPIO5; |
| REG_WR(bp, reg_offset, val); |
| |
| BNX2X_ERR("SPIO5 hw attention\n"); |
| |
| /* Fan failure attention */ |
| switch (XGXS_EXT_PHY_TYPE(bp->link_params.ext_phy_config)) { |
| case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_SFX7101: |
| /* Low power mode is controlled by GPIO 2 */ |
| bnx2x_set_gpio(bp, MISC_REGISTERS_GPIO_2, |
| MISC_REGISTERS_GPIO_OUTPUT_LOW, port); |
| /* The PHY reset is controlled by GPIO 1 */ |
| bnx2x_set_gpio(bp, MISC_REGISTERS_GPIO_1, |
| MISC_REGISTERS_GPIO_OUTPUT_LOW, port); |
| break; |
| |
| case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_BCM8727: |
| /* The PHY reset is controlled by GPIO 1 */ |
| /* fake the port number to cancel the swap done in |
| set_gpio() */ |
| swap_val = REG_RD(bp, NIG_REG_PORT_SWAP); |
| swap_override = REG_RD(bp, NIG_REG_STRAP_OVERRIDE); |
| port = (swap_val && swap_override) ^ 1; |
| bnx2x_set_gpio(bp, MISC_REGISTERS_GPIO_1, |
| MISC_REGISTERS_GPIO_OUTPUT_LOW, port); |
| break; |
| |
| default: |
| break; |
| } |
| bnx2x_fan_failure(bp); |
| } |
| |
| if (attn & (AEU_INPUTS_ATTN_BITS_GPIO3_FUNCTION_0 | |
| AEU_INPUTS_ATTN_BITS_GPIO3_FUNCTION_1)) { |
| bnx2x_acquire_phy_lock(bp); |
| bnx2x_handle_module_detect_int(&bp->link_params); |
| bnx2x_release_phy_lock(bp); |
| } |
| |
| if (attn & HW_INTERRUT_ASSERT_SET_0) { |
| |
| val = REG_RD(bp, reg_offset); |
| val &= ~(attn & HW_INTERRUT_ASSERT_SET_0); |
| REG_WR(bp, reg_offset, val); |
| |
| BNX2X_ERR("FATAL HW block attention set0 0x%x\n", |
| (u32)(attn & HW_INTERRUT_ASSERT_SET_0)); |
| bnx2x_panic(); |
| } |
| } |
| |
| static inline void bnx2x_attn_int_deasserted1(struct bnx2x *bp, u32 attn) |
| { |
| u32 val; |
| |
| if (attn & AEU_INPUTS_ATTN_BITS_DOORBELLQ_HW_INTERRUPT) { |
| |
| val = REG_RD(bp, DORQ_REG_DORQ_INT_STS_CLR); |
| BNX2X_ERR("DB hw attention 0x%x\n", val); |
| /* DORQ discard attention */ |
| if (val & 0x2) |
| BNX2X_ERR("FATAL error from DORQ\n"); |
| } |
| |
| if (attn & HW_INTERRUT_ASSERT_SET_1) { |
| |
| int port = BP_PORT(bp); |
| int reg_offset; |
| |
| reg_offset = (port ? MISC_REG_AEU_ENABLE1_FUNC_1_OUT_1 : |
| MISC_REG_AEU_ENABLE1_FUNC_0_OUT_1); |
| |
| val = REG_RD(bp, reg_offset); |
| val &= ~(attn & HW_INTERRUT_ASSERT_SET_1); |
| REG_WR(bp, reg_offset, val); |
| |
| BNX2X_ERR("FATAL HW block attention set1 0x%x\n", |
| (u32)(attn & HW_INTERRUT_ASSERT_SET_1)); |
| bnx2x_panic(); |
| } |
| } |
| |
| static inline void bnx2x_attn_int_deasserted2(struct bnx2x *bp, u32 attn) |
| { |
| u32 val; |
| |
| if (attn & AEU_INPUTS_ATTN_BITS_CFC_HW_INTERRUPT) { |
| |
| val = REG_RD(bp, CFC_REG_CFC_INT_STS_CLR); |
| BNX2X_ERR("CFC hw attention 0x%x\n", val); |
| /* CFC error attention */ |
| if (val & 0x2) |
| BNX2X_ERR("FATAL error from CFC\n"); |
| } |
|