[PATCH v3 for-next 05/24] RDMA/hfi2: Add in HW register access support
Dennis Dalessandro <[email protected]> Mon, 03 Aug 2026 12:01:28 -0400
| Newsgroups | org.kernel.vger.linux-rdma |
|---|---|
| Message-ID | <178577288825.1792062.12336696323498041670.stgit@awdrv-04> |
Add the HW register access implementation files for the hfi2 driver. chip.c provides the core CSR read/write operations, link management, interrupt handling, and chip initialization support for both WFR and JKR generations. chip_gen.c and chip_jkr.c provide the generation-specific implementations. Also add sriov.c and vf2pf.c for SR-IOV and VF-to-PF communication support, and exp_rcv.h for expected receive definitions. Co-developed-by: Dean Luick <[email protected]> Signed-off-by: Dean Luick <[email protected]> Co-developed-by: Brendan Cunningham <[email protected]> Signed-off-by: Breandan Cunningham <[email protected]> Co-developed-by: Douglas Miller <[email protected]> Signed-off-by: Douglas Miller <[email protected]> Assisted-by: Claude:claude-sonnet-4-5 Signed-off-by: Dennis Dalessandro <[email protected]> --- Changes since v1: - Implement jkr_handle_link_bounce() and resolve FIXME stubs (Jason Gunthorpe). - Remove custom ASPM handling (Leon Romanovsky). - Replace cport accessor macros with inline functions (Arnd Bergmann). --- drivers/infiniband/hw/hfi2/chip.c |12946 +++++++++++++++++++++++++++++++++ drivers/infiniband/hw/hfi2/chip_gen.c | 1151 +++ drivers/infiniband/hw/hfi2/chip_jkr.c | 1017 +++ drivers/infiniband/hw/hfi2/exp_rcv.h | 157 drivers/infiniband/hw/hfi2/sriov.c | 429 + drivers/infiniband/hw/hfi2/vf2pf.c | 1106 +++ drivers/infiniband/hw/hfi2/vf2pf_lb.c | 964 ++ 7 files changed, 17770 insertions(+) create mode 100644 drivers/infiniband/hw/hfi2/chip.c create mode 100644 drivers/infiniband/hw/hfi2/chip_gen.c create mode 100644 drivers/infiniband/hw/hfi2/chip_jkr.c create mode 100644 drivers/infiniband/hw/hfi2/exp_rcv.h create mode 100644 drivers/infiniband/hw/hfi2/sriov.c create mode 100644 drivers/infiniband/hw/hfi2/vf2pf.c create mode 100644 drivers/infiniband/hw/hfi2/vf2pf_lb.c diff --git a/drivers/infiniband/hw/hfi2/chip.c b/drivers/infiniband/hw/hfi2/chip.c new file mode 100644 index 000000000000..797f7904ab53 --- /dev/null +++ b/drivers/infiniband/hw/hfi2/chip.c @@ -0,0 +1,12946 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright(c) 2015 - 2020 Intel Corporation. + * Copyright(c) 2025-2026 Cornelis Networks, Inc. + */ + +/* + * This file contains all of the code that is specific to the HFI chip + */ + +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/module.h> + +#include "hfi2.h" +#include "affinity.h" +#include "file_ops.h" +#include "trace.h" +#include "mad.h" +#include "pio.h" +#include "sdma.h" +#include "eprom.h" +#include "efivar.h" +#include "platform.h" +#include "debugfs.h" +#include "fault.h" +#include "netdev.h" +#include "chip_registers_jkr.h" +#include "vf2pf.h" +#include "sriov.h" + +uint hfi2_num_vls = HFI2_MAX_VLS_SUPPORTED; + +/* + * Default time to aggregate two 10K packets from the idle state + * (timer not running). The timer starts at the end of the first packet, + * so only the time for one 10K packet and header plus a bit extra is needed. + * 10 * 1024 + 64 header byte = 10304 byte + * 10304 byte / 12.5 GB/s = 824.32ns + */ +uint hfi2_rcv_intr_timeout = (824 + 16); /* 16 is for coalescing interrupt */ + +uint hfi2_rcv_intr_count = 16; /* same as qib */ + +ushort hfi2_link_crc_mask = SUPPORTED_CRCS; + +uint hfi2_loopback; + +static int sdma_yield = 1000; /* how often to yield when in thrd intr handler */ + +static const int rcvwcb = -1; /* RcvIportCtrl.RcvWcb setting */ + +/* Other driver tunables */ +uint hfi2_rcv_intr_dynamic = 1; /* enable dynamic mode for rcv int mitigation*/ +static ushort crc_14b_sideband = 1; +static uint use_flr = 1; +uint hfi2_quick_linkup; /* skip LNI */ + +/* str must be a string constant */ +#define FLAG_ENTRY(str, extra, flag) \ + { \ + flag, str, extra \ + } +#define FLAG_ENTRY0(str, flag) \ + { \ + flag, str, 0 \ + } + +/* Send Error Consequences */ +#define SEC_WRITE_DROPPED 0x1 +#define SEC_PACKET_DROPPED 0x2 +#define SEC_SC_HALTED 0x4 /* per-context only */ +#define SEC_SPC_FREEZE 0x8 /* per-HFI only */ + +#define DEFAULT_KRCVQS 2 +#define MIN_KERNEL_KCTXTS 2 +#define FIRST_KERNEL_KCTXT 1 + +/* + * RSM types + */ +#define RSM_TYPE_FECN 0 +#define RSM_TYPE_DEPRECATED 1 +#define RSM_TYPE_AIP 2 +#define RSM_TYPE_VERBS 3 +#define RSM_TYPE_MAD_RSP 4 +#define RSM_TYPE_MAD_ACTION 5 + +/* Bit offset into the GUID which carries HFI id information */ +#define GUID_HFI_INDEX_SHIFT 39 + +/* RSM fields for Verbs */ +#define QW_SHIFT 6ull +/* QPN[7..1] */ +#define QPN_WIDTH 7ull + +/* LRH.BTH: QW 0, OFFSET 48 - for match */ +#define LRH_BTH_QW 0ull +#define LRH_BTH_BIT_OFFSET 48ull +#define LRH_BTH_OFFSET(off) ((LRH_BTH_QW << QW_SHIFT) | (off)) +#define LRH_BTH_MATCH_OFFSET LRH_BTH_OFFSET(LRH_BTH_BIT_OFFSET) +#define LRH_BTH_SELECT +#define LRH_BTH_MASK 3ull +#define LRH_BTH_VALUE 2ull + +/* LRH.SC[3..0] QW 0, OFFSET 56 - for match */ +#define LRH_SC_QW 0ull +#define LRH_SC_BIT_OFFSET 56ull +#define LRH_SC_OFFSET(off) ((LRH_SC_QW << QW_SHIFT) | (off)) +#define LRH_SC_MATCH_OFFSET LRH_SC_OFFSET(LRH_SC_BIT_OFFSET) +#define LRH_SC_MASK 128ull +#define LRH_SC_VALUE 0ull + +/* SC[n..0] QW 0, OFFSET 60 - for select */ +#define LRH_SC_SELECT_OFFSET ((LRH_SC_QW << QW_SHIFT) | (60ull)) + +/* QPN[m+n:1] QW 1, OFFSET 1 */ +#define QPN_SELECT_OFFSET ((1ull << QW_SHIFT) | (1ull)) + +/* RSM fields for AIP */ +/* LRH.BTH above is reused for this rule */ + +/* BTH.DESTQP: QW 1, OFFSET 16 for match */ +#define BTH_DESTQP_QW 1ull +#define BTH_DESTQP_BIT_OFFSET 16ull +#define BTH_DESTQP_OFFSET(off) ((BTH_DESTQP_QW << QW_SHIFT) | (off)) +#define BTH_DESTQP_MATCH_OFFSET BTH_DESTQP_OFFSET(BTH_DESTQP_BIT_OFFSET) +#define BTH_DESTQP_MASK 0xFFull +#define BTH_DESTQP_VALUE 0x81ull + +/* DETH.SQPN: QW 1 Offset 56 for select */ +/* We use 8 most significant Soure QPN bits as entropy fpr AIP */ +#define DETH_AIP_SQPN_QW 3ull +#define DETH_AIP_SQPN_BIT_OFFSET 56ull +#define DETH_AIP_SQPN_OFFSET(off) ((DETH_AIP_SQPN_QW << QW_SHIFT) | (off)) +#define DETH_AIP_SQPN_SELECT_OFFSET \ + DETH_AIP_SQPN_OFFSET(DETH_AIP_SQPN_BIT_OFFSET) + +/* L4_TYPE QW 1, OFFSET 0 - for match */ +#define L4_TYPE_QW 1ull +#define L4_TYPE_BIT_OFFSET 0ull +#define L4_TYPE_OFFSET(off) ((L4_TYPE_QW << QW_SHIFT) | (off)) +#define L4_TYPE_MATCH_OFFSET L4_TYPE_OFFSET(L4_TYPE_BIT_OFFSET) +#define L4_16B_TYPE_MASK 0xFFull +#define L4_16B_ETH_VALUE 0x78ull + +/* 16B VESWID - for select */ +#define L4_16B_HDR_VESWID_OFFSET ((2 << QW_SHIFT) | (16ull)) +/* 16B ENTROPY - for select */ +#define L2_16B_ENTROPY_OFFSET ((1 << QW_SHIFT) | (32ull)) + +/* defines to build power on SC2VL table */ +#define SC2VL_VAL(num, sc0, sc0val, sc1, sc1val, sc2, sc2val, sc3, sc3val, \ + sc4, sc4val, sc5, sc5val, sc6, sc6val, sc7, sc7val) \ + (((u64)(sc0val) << SEND_SC2VLT##num##_SC##sc0##_SHIFT) | \ + ((u64)(sc1val) << SEND_SC2VLT##num##_SC##sc1##_SHIFT) | \ + ((u64)(sc2val) << SEND_SC2VLT##num##_SC##sc2##_SHIFT) | \ + ((u64)(sc3val) << SEND_SC2VLT##num##_SC##sc3##_SHIFT) | \ + ((u64)(sc4val) << SEND_SC2VLT##num##_SC##sc4##_SHIFT) | \ + ((u64)(sc5val) << SEND_SC2VLT##num##_SC##sc5##_SHIFT) | \ + ((u64)(sc6val) << SEND_SC2VLT##num##_SC##sc6##_SHIFT) | \ + ((u64)(sc7val) << SEND_SC2VLT##num##_SC##sc7##_SHIFT)) + +#define DC_SC_VL_VAL(range, e0, e0val, e1, e1val, e2, e2val, e3, e3val, e4, \ + e4val, e5, e5val, e6, e6val, e7, e7val, e8, e8val, e9, \ + e9val, e10, e10val, e11, e11val, e12, e12val, e13, \ + e13val, e14, e14val, e15, e15val) \ + (((u64)(e0val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e0##_SHIFT) | \ + ((u64)(e1val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e1##_SHIFT) | \ + ((u64)(e2val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e2##_SHIFT) | \ + ((u64)(e3val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e3##_SHIFT) | \ + ((u64)(e4val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e4##_SHIFT) | \ + ((u64)(e5val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e5##_SHIFT) | \ + ((u64)(e6val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e6##_SHIFT) | \ + ((u64)(e7val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e7##_SHIFT) | \ + ((u64)(e8val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e8##_SHIFT) | \ + ((u64)(e9val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e9##_SHIFT) | \ + ((u64)(e10val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e10##_SHIFT) | \ + ((u64)(e11val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e11##_SHIFT) | \ + ((u64)(e12val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e12##_SHIFT) | \ + ((u64)(e13val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e13##_SHIFT) | \ + ((u64)(e14val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e14##_SHIFT) | \ + ((u64)(e15val) << DCC_CFG_SC_VL_TABLE_##range##_ENTRY##e15##_SHIFT)) + +/* all CceStatus sub-block freeze bits */ +#define ALL_FROZE \ + (CCE_STATUS_SDMA_FROZE_SMASK | CCE_STATUS_RXE_FROZE_SMASK | \ + CCE_STATUS_TXE_FROZE_SMASK | CCE_STATUS_TXE_PIO_FROZE_SMASK) +/* all CceStatus sub-block TXE pause bits */ +#define ALL_TXE_PAUSE \ + (CCE_STATUS_TXE_PIO_PAUSED_SMASK | CCE_STATUS_TXE_PAUSED_SMASK | \ + CCE_STATUS_SDMA_PAUSED_SMASK) +/* all CceStatus sub-block RXE pause bits */ +#define ALL_RXE_PAUSE CCE_STATUS_RXE_PAUSED_SMASK + +#define CNTR_MAX 0xFFFFFFFFFFFFFFFFULL +#define CNTR_32BIT_MAX 0x00000000FFFFFFFF + +/* + * CCE Error flags. + */ +static const struct flag_table cce_err_status_flags[] = { + /* 0*/ FLAG_ENTRY0("CceCsrParityErr", + CCE_ERR_STATUS_CCE_CSR_PARITY_ERR_SMASK), + /* 1*/ + FLAG_ENTRY0("CceCsrReadBadAddrErr", + CCE_ERR_STATUS_CCE_CSR_READ_BAD_ADDR_ERR_SMASK), + /* 2*/ + FLAG_ENTRY0("CceCsrWriteBadAddrErr", + CCE_ERR_STATUS_CCE_CSR_WRITE_BAD_ADDR_ERR_SMASK), + /* 3*/ + FLAG_ENTRY0("CceTrgtAsyncFifoParityErr", + CCE_ERR_STATUS_CCE_TRGT_ASYNC_FIFO_PARITY_ERR_SMASK), + /* 4*/ + FLAG_ENTRY0("CceTrgtAccessErr", + CCE_ERR_STATUS_CCE_TRGT_ACCESS_ERR_SMASK), + /* 5*/ + FLAG_ENTRY0("CceRspdDataParityErr", + CCE_ERR_STATUS_CCE_RSPD_DATA_PARITY_ERR_SMASK), + /* 6*/ + FLAG_ENTRY0("CceCli0AsyncFifoParityErr", + CCE_ERR_STATUS_CCE_CLI0_ASYNC_FIFO_PARITY_ERR_SMASK), + /* 7*/ + FLAG_ENTRY0("CceCsrCfgBusParityErr", + CCE_ERR_STATUS_CCE_CSR_CFG_BUS_PARITY_ERR_SMASK), + /* 8*/ + FLAG_ENTRY0("CceCli2AsyncFifoParityErr", + CCE_ERR_STATUS_CCE_CLI2_ASYNC_FIFO_PARITY_ERR_SMASK), + /* 9*/ + FLAG_ENTRY0( + "CceCli1AsyncFifoPioCrdtParityErr", + CCE_ERR_STATUS_CCE_CLI1_ASYNC_FIFO_PIO_CRDT_PARITY_ERR_SMASK), + /*10*/ + FLAG_ENTRY0("CceCli1AsyncFifoPioCrdtParityErr", + CCE_ERR_STATUS_CCE_CLI1_ASYNC_FIFO_SDMA_HD_PARITY_ERR_SMASK), + /*11*/ + FLAG_ENTRY0("CceCli1AsyncFifoRxdmaParityError", + CCE_ERR_STATUS_CCE_CLI1_ASYNC_FIFO_RXDMA_PARITY_ERROR_SMASK), + /*12*/ + FLAG_ENTRY0("CceCli1AsyncFifoDbgParityError", + CCE_ERR_STATUS_CCE_CLI1_ASYNC_FIFO_DBG_PARITY_ERROR_SMASK), + /*13*/ + FLAG_ENTRY0("PcicRetryMemCorErr", + CCE_ERR_STATUS_PCIC_RETRY_MEM_COR_ERR_SMASK), + /*14*/ + FLAG_ENTRY0("PcicRetryMemCorErr", + CCE_ERR_STATUS_PCIC_RETRY_SOT_MEM_COR_ERR_SMASK), + /*15*/ + FLAG_ENTRY0("PcicPostHdQCorErr", + CCE_ERR_STATUS_PCIC_POST_HD_QCOR_ERR_SMASK), + /*16*/ + FLAG_ENTRY0("PcicPostHdQCorErr", + CCE_ERR_STATUS_PCIC_POST_DAT_QCOR_ERR_SMASK), + /*17*/ + FLAG_ENTRY0("PcicPostHdQCorErr", + CCE_ERR_STATUS_PCIC_CPL_HD_QCOR_ERR_SMASK), + /*18*/ + FLAG_ENTRY0("PcicCplDatQCorErr", + CCE_ERR_STATUS_PCIC_CPL_DAT_QCOR_ERR_SMASK), + /*19*/ + FLAG_ENTRY0("PcicNPostHQParityErr", + CCE_ERR_STATUS_PCIC_NPOST_HQ_PARITY_ERR_SMASK), + /*20*/ + FLAG_ENTRY0("PcicNPostDatQParityErr", + CCE_ERR_STATUS_PCIC_NPOST_DAT_QPARITY_ERR_SMASK), + /*21*/ + FLAG_ENTRY0("PcicRetryMemUncErr", + CCE_ERR_STATUS_PCIC_RETRY_MEM_UNC_ERR_SMASK), + /*22*/ + FLAG_ENTRY0("PcicRetrySotMemUncErr", + CCE_ERR_STATUS_PCIC_RETRY_SOT_MEM_UNC_ERR_SMASK), + /*23*/ + FLAG_ENTRY0("PcicPostHdQUncErr", + CCE_ERR_STATUS_PCIC_POST_HD_QUNC_ERR_SMASK), + /*24*/ + FLAG_ENTRY0("PcicPostDatQUncErr", + CCE_ERR_STATUS_PCIC_POST_DAT_QUNC_ERR_SMASK), + /*25*/ + FLAG_ENTRY0("PcicCplHdQUncErr", + CCE_ERR_STATUS_PCIC_CPL_HD_QUNC_ERR_SMASK), + /*26*/ + FLAG_ENTRY0("PcicCplDatQUncErr", + CCE_ERR_STATUS_PCIC_CPL_DAT_QUNC_ERR_SMASK), + /*27*/ + FLAG_ENTRY0("PcicTransmitFrontParityErr", + CCE_ERR_STATUS_PCIC_TRANSMIT_FRONT_PARITY_ERR_SMASK), + /*28*/ + FLAG_ENTRY0("PcicTransmitBackParityErr", + CCE_ERR_STATUS_PCIC_TRANSMIT_BACK_PARITY_ERR_SMASK), + /*29*/ + FLAG_ENTRY0("PcicReceiveParityErr", + CCE_ERR_STATUS_PCIC_RECEIVE_PARITY_ERR_SMASK), + /*30*/ + FLAG_ENTRY0("CceTrgtCplTimeoutErr", + CCE_ERR_STATUS_CCE_TRGT_CPL_TIMEOUT_ERR_SMASK), + /*31*/ FLAG_ENTRY0("LATriggered", CCE_ERR_STATUS_LA_TRIGGERED_SMASK), + /*32*/ + FLAG_ENTRY0("CceSegReadBadAddrErr", + CCE_ERR_STATUS_CCE_SEG_READ_BAD_ADDR_ERR_SMASK), + /*33*/ + FLAG_ENTRY0("CceSegWriteBadAddrErr", + CCE_ERR_STATUS_CCE_SEG_WRITE_BAD_ADDR_ERR_SMASK), + /*34*/ + FLAG_ENTRY0("CceRcplAsyncFifoParityErr", + CCE_ERR_STATUS_CCE_RCPL_ASYNC_FIFO_PARITY_ERR_SMASK), + /*35*/ + FLAG_ENTRY0("CceRxdmaConvFifoParityErr", + CCE_ERR_STATUS_CCE_RXDMA_CONV_FIFO_PARITY_ERR_SMASK), + /*36*/ + FLAG_ENTRY0("CceMsixTableCorErr", + CCE_ERR_STATUS_CCE_MSIX_TABLE_COR_ERR_SMASK), + /*37*/ + FLAG_ENTRY0("CceMsixTableUncErr", + CCE_ERR_STATUS_CCE_MSIX_TABLE_UNC_ERR_SMASK), + /*38*/ + FLAG_ENTRY0("CceIntMapCorErr", + CCE_ERR_STATUS_CCE_INT_MAP_COR_ERR_SMASK), + /*39*/ + FLAG_ENTRY0("CceIntMapUncErr", + CCE_ERR_STATUS_CCE_INT_MAP_UNC_ERR_SMASK), + /*40*/ + FLAG_ENTRY0("CceMsixCsrParityErr", + CCE_ERR_STATUS_CCE_MSIX_CSR_PARITY_ERR_SMASK), + /*41-63 reserved*/ +}; + +/* + * Misc Error flags + */ +#define MES(text) MISC_ERR_STATUS_MISC_##text##_ERR_SMASK +static const struct flag_table misc_err_status_flags[] = { + /* 0*/ FLAG_ENTRY0("CSR_PARITY", MES(CSR_PARITY)), + /* 1*/ FLAG_ENTRY0("CSR_READ_BAD_ADDR", MES(CSR_READ_BAD_ADDR)), + /* 2*/ FLAG_ENTRY0("CSR_WRITE_BAD_ADDR", MES(CSR_WRITE_BAD_ADDR)), + /* 3*/ FLAG_ENTRY0("SBUS_WRITE_FAILED", MES(SBUS_WRITE_FAILED)), + /* 4*/ FLAG_ENTRY0("KEY_MISMATCH", MES(KEY_MISMATCH)), + /* 5*/ FLAG_ENTRY0("FW_AUTH_FAILED", MES(FW_AUTH_FAILED)), + /* 6*/ FLAG_ENTRY0("EFUSE_CSR_PARITY", MES(EFUSE_CSR_PARITY)), + /* 7*/ FLAG_ENTRY0("EFUSE_READ_BAD_ADDR", MES(EFUSE_READ_BAD_ADDR)), + /* 8*/ FLAG_ENTRY0("EFUSE_WRITE", MES(EFUSE_WRITE)), + /* 9*/ FLAG_ENTRY0("EFUSE_DONE_PARITY", MES(EFUSE_DONE_PARITY)), + /*10*/ FLAG_ENTRY0("INVALID_EEP_CMD", MES(INVALID_EEP_CMD)), + /*11*/ FLAG_ENTRY0("MBIST_FAIL", MES(MBIST_FAIL)), + /*12*/ FLAG_ENTRY0("PLL_LOCK_FAIL", MES(PLL_LOCK_FAIL)) +}; + +/* + * TXE PIO Error flags and consequences + */ +static const struct flag_table pio_err_status_flags[] = { + /* 0*/ FLAG_ENTRY("PioWriteBadCtxt", SEC_WRITE_DROPPED, + SEND_PIO_ERR_STATUS_PIO_WRITE_BAD_CTXT_ERR_SMASK), + /* 1*/ + FLAG_ENTRY("PioWriteAddrParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_WRITE_ADDR_PARITY_ERR_SMASK), + /* 2*/ + FLAG_ENTRY("PioCsrParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_CSR_PARITY_ERR_SMASK), + /* 3*/ + FLAG_ENTRY("PioSbMemFifo0", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_SB_MEM_FIFO0_ERR_SMASK), + /* 4*/ + FLAG_ENTRY("PioSbMemFifo1", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_SB_MEM_FIFO1_ERR_SMASK), + /* 5*/ + FLAG_ENTRY("PioPccFifoParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PCC_FIFO_PARITY_ERR_SMASK), + /* 6*/ + FLAG_ENTRY("PioPecFifoParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PEC_FIFO_PARITY_ERR_SMASK), + /* 7*/ + FLAG_ENTRY("PioSbrdctlCrrelParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_SBRDCTL_CRREL_PARITY_ERR_SMASK), + /* 8*/ + FLAG_ENTRY("PioSbrdctrlCrrelFifoParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_SBRDCTRL_CRREL_FIFO_PARITY_ERR_SMASK), + /* 9*/ + FLAG_ENTRY("PioPktEvictFifoParityErr", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PKT_EVICT_FIFO_PARITY_ERR_SMASK), + /*10*/ + FLAG_ENTRY("PioSmPktResetParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_SM_PKT_RESET_PARITY_ERR_SMASK), + /*11*/ + FLAG_ENTRY("PioVlLenMemBank0Unc", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_VL_LEN_MEM_BANK0_UNC_ERR_SMASK), + /*12*/ + FLAG_ENTRY("PioVlLenMemBank1Unc", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_VL_LEN_MEM_BANK1_UNC_ERR_SMASK), + /*13*/ + FLAG_ENTRY("PioVlLenMemBank0Cor", 0, + SEND_PIO_ERR_STATUS_PIO_VL_LEN_MEM_BANK0_COR_ERR_SMASK), + /*14*/ + FLAG_ENTRY("PioVlLenMemBank1Cor", 0, + SEND_PIO_ERR_STATUS_PIO_VL_LEN_MEM_BANK1_COR_ERR_SMASK), + /*15*/ + FLAG_ENTRY("PioCreditRetFifoParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_CREDIT_RET_FIFO_PARITY_ERR_SMASK), + /*16*/ + FLAG_ENTRY("PioPpmcPblFifo", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PPMC_PBL_FIFO_ERR_SMASK), + /*17*/ + FLAG_ENTRY("PioInitSmIn", 0, + SEND_PIO_ERR_STATUS_PIO_INIT_SM_IN_ERR_SMASK), + /*18*/ + FLAG_ENTRY("PioPktEvictSmOrArbSm", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PKT_EVICT_SM_OR_ARB_SM_ERR_SMASK), + /*19*/ + FLAG_ENTRY("PioHostAddrMemUnc", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_HOST_ADDR_MEM_UNC_ERR_SMASK), + /*20*/ + FLAG_ENTRY("PioHostAddrMemCor", 0, + SEND_PIO_ERR_STATUS_PIO_HOST_ADDR_MEM_COR_ERR_SMASK), + /*21*/ + FLAG_ENTRY("PioWriteDataParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_WRITE_DATA_PARITY_ERR_SMASK), + /*22*/ + FLAG_ENTRY("PioStateMachine", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_STATE_MACHINE_ERR_SMASK), + /*23*/ + FLAG_ENTRY("PioWriteQwValidParity", SEC_WRITE_DROPPED | SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_WRITE_QW_VALID_PARITY_ERR_SMASK), + /*24*/ + FLAG_ENTRY("PioBlockQwCountParity", SEC_WRITE_DROPPED | SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_BLOCK_QW_COUNT_PARITY_ERR_SMASK), + /*25*/ + FLAG_ENTRY("PioVlfVlLenParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_VLF_VL_LEN_PARITY_ERR_SMASK), + /*26*/ + FLAG_ENTRY("PioVlfSopParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_VLF_SOP_PARITY_ERR_SMASK), + /*27*/ + FLAG_ENTRY("PioVlFifoParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_VL_FIFO_PARITY_ERR_SMASK), + /*28*/ + FLAG_ENTRY("PioPpmcBqcMemParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PPMC_BQC_MEM_PARITY_ERR_SMASK), + /*29*/ + FLAG_ENTRY("PioPpmcSopLen", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PPMC_SOP_LEN_ERR_SMASK), + /*30-31 reserved*/ + /*32*/ + FLAG_ENTRY("PioCurrentFreeCntParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_CURRENT_FREE_CNT_PARITY_ERR_SMASK), + /*33*/ + FLAG_ENTRY("PioLastReturnedCntParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_LAST_RETURNED_CNT_PARITY_ERR_SMASK), + /*34*/ + FLAG_ENTRY("PioPccSopHeadParity", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PCC_SOP_HEAD_PARITY_ERR_SMASK), + /*35*/ + FLAG_ENTRY("PioPecSopHeadParityErr", SEC_SPC_FREEZE, + SEND_PIO_ERR_STATUS_PIO_PEC_SOP_HEAD_PARITY_ERR_SMASK), + /*36-63 reserved*/ +}; + +/* TXE PIO errors that cause an SPC freeze */ +#define ALL_PIO_FREEZE_ERR \ + (SEND_PIO_ERR_STATUS_PIO_WRITE_ADDR_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_CSR_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_SB_MEM_FIFO0_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_SB_MEM_FIFO1_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PCC_FIFO_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PEC_FIFO_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_SBRDCTL_CRREL_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_SBRDCTRL_CRREL_FIFO_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PKT_EVICT_FIFO_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_SM_PKT_RESET_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_VL_LEN_MEM_BANK0_UNC_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_VL_LEN_MEM_BANK1_UNC_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_CREDIT_RET_FIFO_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PPMC_PBL_FIFO_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PKT_EVICT_SM_OR_ARB_SM_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_HOST_ADDR_MEM_UNC_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_WRITE_DATA_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_STATE_MACHINE_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_WRITE_QW_VALID_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_BLOCK_QW_COUNT_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_VLF_VL_LEN_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_VLF_SOP_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_VL_FIFO_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PPMC_BQC_MEM_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PPMC_SOP_LEN_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_CURRENT_FREE_CNT_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_LAST_RETURNED_CNT_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PCC_SOP_HEAD_PARITY_ERR_SMASK | \ + SEND_PIO_ERR_STATUS_PIO_PEC_SOP_HEAD_PARITY_ERR_SMASK) + +/* + * TXE SDMA Error flags + */ +static const struct flag_table sdma_err_status_flags[] = { + /* 0*/ FLAG_ENTRY0("SDmaRpyTagErr", + SEND_DMA_ERR_STATUS_SDMA_RPY_TAG_ERR_SMASK), + /* 1*/ + FLAG_ENTRY0("SDmaCsrParityErr", + SEND_DMA_ERR_STATUS_SDMA_CSR_PARITY_ERR_SMASK), + /* 2*/ + FLAG_ENTRY0("SDmaPcieReqTrackingUncErr", + SEND_DMA_ERR_STATUS_SDMA_PCIE_REQ_TRACKING_UNC_ERR_SMASK), + /* 3*/ + FLAG_ENTRY0("SDmaPcieReqTrackingCorErr", + SEND_DMA_ERR_STATUS_SDMA_PCIE_REQ_TRACKING_COR_ERR_SMASK), + /*04-63 reserved*/ +}; + +/* TXE SDMA errors that cause an SPC freeze */ +#define ALL_SDMA_FREEZE_ERR \ + (SEND_DMA_ERR_STATUS_SDMA_RPY_TAG_ERR_SMASK | \ + SEND_DMA_ERR_STATUS_SDMA_CSR_PARITY_ERR_SMASK | \ + SEND_DMA_ERR_STATUS_SDMA_PCIE_REQ_TRACKING_UNC_ERR_SMASK) + +/* + * TXE Egress Error flags + */ +#define SEES(text) SEND_EGRESS_ERR_STATUS_##text##_ERR_SMASK +static const struct flag_table egress_err_status_flags[] = { + /* 0*/ FLAG_ENTRY0("TxPktIntegrityMemCorErr", + SEES(TX_PKT_INTEGRITY_MEM_COR)), /* WFR */ + /* 1*/ + FLAG_ENTRY0("TxPktIntegrityMemUncErr", + SEES(TX_PKT_INTEGRITY_MEM_UNC)), /* WFR */ + /* 2 reserved */ + /* 3*/ + FLAG_ENTRY0("TxEgressFifoUnderrunOrParityErr", + SEES(TX_EGRESS_FIFO_UNDERRUN_OR_PARITY)), + /* 4*/ FLAG_ENTRY0("TxLinkdownErr", SEES(TX_LINKDOWN)), + /* 5*/ + FLAG_ENTRY0("TxIncorrectLinkStateErr", SEES(TX_INCORRECT_LINK_STATE)), + /* 6 reserved */ + /* 7*/ + FLAG_ENTRY0("TxPioLaunchIntfParityErr", + SEES(TX_PIO_LAUNCH_INTF_PARITY)), + /* 8*/ + FLAG_ENTRY0("TxSdmaLaunchIntfParityErr", + SEES(TX_SDMA_LAUNCH_INTF_PARITY)), + /* 9-10 reserved */ + /*11*/ + FLAG_ENTRY0("TxSbrdCtlStateMachineParityErr", + SEES(TX_SBRD_CTL_STATE_MACHINE_PARITY)), + /*12*/ FLAG_ENTRY0("TxIllegalVLErr", SEES(TX_ILLEGAL_VL)), + /*13*/ FLAG_ENTRY0("TxLaunchCsrParityErr", SEES(TX_LAUNCH_CSR_PARITY)), /* WFR */ + /*14*/ FLAG_ENTRY0("TxSbrdCtlCsrParityErr", SEES(TX_SBRD_CTL_CSR_PARITY)), + /*15*/ FLAG_ENTRY0("TxConfigParityErr", SEES(TX_CONFIG_PARITY)), + /*16*/ + FLAG_ENTRY0("TxSdma0DisallowedPacketErr", + SEES(TX_SDMA0_DISALLOWED_PACKET)), + /*17*/ + FLAG_ENTRY0("TxSdma1DisallowedPacketErr", + SEES(TX_SDMA1_DISALLOWED_PACKET)), + /*18*/ + FLAG_ENTRY0("TxSdma2DisallowedPacketErr", + SEES(TX_SDMA2_DISALLOWED_PACKET)), + /*19*/ + FLAG_ENTRY0("TxSdma3DisallowedPacketErr", + SEES(TX_SDMA3_DISALLOWED_PACKET)), + /*20*/ + FLAG_ENTRY0("TxSdma4DisallowedPacketErr", + SEES(TX_SDMA4_DISALLOWED_PACKET)), + /*21*/ + FLAG_ENTRY0("TxSdma5DisallowedPacketErr", + SEES(TX_SDMA5_DISALLOWED_PACKET)), + /*22*/ + FLAG_ENTRY0("TxSdma6DisallowedPacketErr", + SEES(TX_SDMA6_DISALLOWED_PACKET)), + /*23*/ + FLAG_ENTRY0("TxSdma7DisallowedPacketErr", + SEES(TX_SDMA7_DISALLOWED_PACKET)), + /*24*/ + FLAG_ENTRY0("TxSdma8DisallowedPacketErr", + SEES(TX_SDMA8_DISALLOWED_PACKET)), + /*25*/ + FLAG_ENTRY0("TxSdma9DisallowedPacketErr", + SEES(TX_SDMA9_DISALLOWED_PACKET)), + /*26*/ + FLAG_ENTRY0("TxSdma10DisallowedPacketErr", + SEES(TX_SDMA10_DISALLOWED_PACKET)), + /*27*/ + FLAG_ENTRY0("TxSdma11DisallowedPacketErr", + SEES(TX_SDMA11_DISALLOWED_PACKET)), + /*28*/ + FLAG_ENTRY0("TxSdma12DisallowedPacketErr", + SEES(TX_SDMA12_DISALLOWED_PACKET)), + /*29*/ + FLAG_ENTRY0("TxSdma13DisallowedPacketErr", + SEES(TX_SDMA13_DISALLOWED_PACKET)), + /*30*/ + FLAG_ENTRY0("TxSdma14DisallowedPacketErr", + SEES(TX_SDMA14_DISALLOWED_PACKET)), + /*31*/ + FLAG_ENTRY0("TxSdma15DisallowedPacketErr", + SEES(TX_SDMA15_DISALLOWED_PACKET)), + /*32*/ + FLAG_ENTRY0("TxLaunchFifo0UncOrParityErr", + SEES(TX_LAUNCH_FIFO0_UNC_OR_PARITY)), + /*33*/ + FLAG_ENTRY0("TxLaunchFifo1UncOrParityErr", + SEES(TX_LAUNCH_FIFO1_UNC_OR_PARITY)), + /*34*/ + FLAG_ENTRY0("TxLaunchFifo2UncOrParityErr", + SEES(TX_LAUNCH_FIFO2_UNC_OR_PARITY)), + /*35*/ + FLAG_ENTRY0("TxLaunchFifo3UncOrParityErr", + SEES(TX_LAUNCH_FIFO3_UNC_OR_PARITY)), + /*36*/ + FLAG_ENTRY0("TxLaunchFifo4UncOrParityErr", + SEES(TX_LAUNCH_FIFO4_UNC_OR_PARITY)), + /*37*/ + FLAG_ENTRY0("TxLaunchFifo5UncOrParityErr", + SEES(TX_LAUNCH_FIFO5_UNC_OR_PARITY)), + /*38*/ + FLAG_ENTRY0("TxLaunchFifo6UncOrParityErr", + SEES(TX_LAUNCH_FIFO6_UNC_OR_PARITY)), + /*39*/ + FLAG_ENTRY0("TxLaunchFifo7UncOrParityErr", + SEES(TX_LAUNCH_FIFO7_UNC_OR_PARITY)), + /*40*/ + FLAG_ENTRY0("TxLaunchFifo8UncOrParityErr", + SEES(TX_LAUNCH_FIFO8_UNC_OR_PARITY)), + /*41*/ + FLAG_ENTRY0("TxCreditReturnParityErr", SEES(TX_CREDIT_RETURN_PARITY)), + /*42*/ FLAG_ENTRY0("TxSbHdrUncErr", SEES(TX_SB_HDR_UNC)), + /*43*/ FLAG_ENTRY0("TxReadSdmaMemoryUncErr", SEES(TX_READ_SDMA_MEMORY_UNC)), + /*44*/ FLAG_ENTRY0("TxReadPioMemoryUncErr", SEES(TX_READ_PIO_MEMORY_UNC)), + /*45*/ FLAG_ENTRY0("TxEgressFifoUncErr", SEES(TX_EGRESS_FIFO_UNC)), + /*46*/ FLAG_ENTRY0("TxHcrcInsertionErr", SEES(TX_HCRC_INSERTION)), + /*47*/ FLAG_ENTRY0("TxCreditReturnVLErr", SEES(TX_CREDIT_RETURN_VL)), + /*48*/ FLAG_ENTRY0("TxLaunchFifo0CorErr", SEES(TX_LAUNCH_FIFO0_COR)), + /*49*/ FLAG_ENTRY0("TxLaunchFifo1CorErr", SEES(TX_LAUNCH_FIFO1_COR)), + /*50*/ FLAG_ENTRY0("TxLaunchFifo2CorErr", SEES(TX_LAUNCH_FIFO2_COR)), + /*51*/ FLAG_ENTRY0("TxLaunchFifo3CorErr", SEES(TX_LAUNCH_FIFO3_COR)), + /*52*/ FLAG_ENTRY0("TxLaunchFifo4CorErr", SEES(TX_LAUNCH_FIFO4_COR)), + /*53*/ FLAG_ENTRY0("TxLaunchFifo5CorErr", SEES(TX_LAUNCH_FIFO5_COR)), + /*54*/ FLAG_ENTRY0("TxLaunchFifo6CorErr", SEES(TX_LAUNCH_FIFO6_COR)), + /*55*/ FLAG_ENTRY0("TxLaunchFifo7CorErr", SEES(TX_LAUNCH_FIFO7_COR)), + /*56*/ FLAG_ENTRY0("TxLaunchFifo8CorErr", SEES(TX_LAUNCH_FIFO8_COR)), + /*57*/ FLAG_ENTRY0("TxCreditOverrunErr", SEES(TX_CREDIT_OVERRUN)), + /*58*/ FLAG_ENTRY0("TxSbHdrCorErr", SEES(TX_SB_HDR_COR)), + /*59*/ FLAG_ENTRY0("TxReadSdmaMemoryCorErr", SEES(TX_READ_SDMA_MEMORY_COR)), + /*60*/ FLAG_ENTRY0("TxReadPioMemoryCorErr", SEES(TX_READ_PIO_MEMORY_COR)), + /*61*/ FLAG_ENTRY0("TxEgressFifoCorErr", SEES(TX_EGRESS_FIFO_COR)), + /*62*/ + FLAG_ENTRY0("TxReadSdmaMemoryCsrUncErr", + SEES(TX_READ_SDMA_MEMORY_CSR_UNC)), /* WFR */ + /*63*/ + FLAG_ENTRY0("TxReadPioMemoryCsrUncErr", + SEES(TX_READ_PIO_MEMORY_CSR_UNC)), /* WFR */ +}; + +/* + * TXE Egress Error Info flags + */ +#define SEEI(text) SEND_EGRESS_ERR_INFO_##text##_ERR_SMASK +static const struct flag_table wfr_egress_err_info_flags[] = { + /* 0*/ FLAG_ENTRY0("Reserved", 0ull), + /* 1*/ FLAG_ENTRY0("VLErr", SEEI(VL)), + /* 2*/ FLAG_ENTRY0("JobKeyErr", SEEI(JOB_KEY)), + /* 3*/ FLAG_ENTRY0("JobKeyErr", SEEI(JOB_KEY)), + /* 4*/ FLAG_ENTRY0("PartitionKeyErr", SEEI(PARTITION_KEY)), + /* 5*/ FLAG_ENTRY0("SLIDErr", SEEI(SLID)), + /* 6*/ FLAG_ENTRY0("OpcodeErr", SEEI(OPCODE)), + /* 7*/ FLAG_ENTRY0("VLMappingErr", SEEI(VL_MAPPING)), + /* 8*/ FLAG_ENTRY0("RawErr", SEEI(RAW)), + /* 9*/ FLAG_ENTRY0("RawIPv6Err", SEEI(RAW_IPV6)), + /*10*/ FLAG_ENTRY0("GRHErr", SEEI(GRH)), + /*11*/ FLAG_ENTRY0("BypassErr", SEEI(BYPASS)), + /*12*/ FLAG_ENTRY0("KDETHPacketsErr", SEEI(KDETH_PACKETS)), + /*13*/ FLAG_ENTRY0("NonKDETHPacketsErr", SEEI(NON_KDETH_PACKETS)), + /*14*/ FLAG_ENTRY0("TooSmallIBPacketsErr", SEEI(TOO_SMALL_IB_PACKETS)), + /*15*/ + FLAG_ENTRY0("TooSmallBypassPacketsErr", SEEI(TOO_SMALL_BYPASS_PACKETS)), + /*16*/ FLAG_ENTRY0("PbcTestErr", SEEI(PBC_TEST)), + /*17*/ FLAG_ENTRY0("BadPktLenErr", SEEI(BAD_PKT_LEN)), + /*18*/ FLAG_ENTRY0("TooLongIBPacketErr", SEEI(TOO_LONG_IB_PACKET)), + /*19*/ + FLAG_ENTRY0("TooLongBypassPacketsErr", SEEI(TOO_LONG_BYPASS_PACKETS)), + /*20*/ + FLAG_ENTRY0("PbcStaticRateControlErr", SEEI(PBC_STATIC_RATE_CONTROL)), + /*21*/ FLAG_ENTRY0("BypassBadPktLenErr", SEEI(BAD_PKT_LEN)), +}; +const struct flag_data hfi2_wfr_egress_err_info_data = { + .table = wfr_egress_err_info_flags, + .size = ARRAY_SIZE(wfr_egress_err_info_flags), +}; + +/* TXE Egress errors that cause an SPC freeze */ +#define ALL_TXE_EGRESS_FREEZE_ERR \ + (SEES(TX_EGRESS_FIFO_UNDERRUN_OR_PARITY) | \ + SEES(TX_PIO_LAUNCH_INTF_PARITY) | SEES(TX_SDMA_LAUNCH_INTF_PARITY) | \ + SEES(TX_SBRD_CTL_STATE_MACHINE_PARITY) | SEES(TX_LAUNCH_CSR_PARITY) | \ + SEES(TX_SBRD_CTL_CSR_PARITY) | SEES(TX_CONFIG_PARITY) | \ + SEES(TX_LAUNCH_FIFO0_UNC_OR_PARITY) | \ + SEES(TX_LAUNCH_FIFO1_UNC_OR_PARITY) | \ + SEES(TX_LAUNCH_FIFO2_UNC_OR_PARITY) | \ + SEES(TX_LAUNCH_FIFO3_UNC_OR_PARITY) | \ + SEES(TX_LAUNCH_FIFO4_UNC_OR_PARITY) | \ + SEES(TX_LAUNCH_FIFO5_UNC_OR_PARITY) | \ + SEES(TX_LAUNCH_FIFO6_UNC_OR_PARITY) | \ + SEES(TX_LAUNCH_FIFO7_UNC_OR_PARITY) | \ + SEES(TX_LAUNCH_FIFO8_UNC_OR_PARITY) | SEES(TX_CREDIT_RETURN_PARITY)) + +/* + * TXE Send error flags + */ +#define SES(name) SEND_ERR_STATUS_SEND_##name##_ERR_SMASK +static const struct flag_table send_err_status_flags[] = { + /* 0*/ FLAG_ENTRY0("SendCsrParityErr", SES(CSR_PARITY)), + /* 1*/ FLAG_ENTRY0("SendCsrReadBadAddrErr", SES(CSR_READ_BAD_ADDR)), + /* 2*/ FLAG_ENTRY0("SendCsrWriteBadAddrErr", SES(CSR_WRITE_BAD_ADDR)) +}; + +/* + * TXE Send Context Error flags and consequences + */ +static const struct flag_table sc_err_status_flags[] = { + /* 0*/ FLAG_ENTRY("InconsistentSop", SEC_PACKET_DROPPED | SEC_SC_HALTED, + SEND_CTXT_ERR_STATUS_PIO_INCONSISTENT_SOP_ERR_SMASK), + /* 1*/ + FLAG_ENTRY("DisallowedPacket", SEC_PACKET_DROPPED | SEC_SC_HALTED, + SEND_CTXT_ERR_STATUS_PIO_DISALLOWED_PACKET_ERR_SMASK), + /* 2*/ + FLAG_ENTRY("WriteCrossesBoundary", SEC_WRITE_DROPPED | SEC_SC_HALTED, + SEND_CTXT_ERR_STATUS_PIO_WRITE_CROSSES_BOUNDARY_ERR_SMASK), + /* 3*/ + FLAG_ENTRY("WriteOverflow", SEC_WRITE_DROPPED | SEC_SC_HALTED, + SEND_CTXT_ERR_STATUS_PIO_WRITE_OVERFLOW_ERR_SMASK), + /* 4*/ + FLAG_ENTRY("WriteOutOfBounds", SEC_WRITE_DROPPED | SEC_SC_HALTED, + SEND_CTXT_ERR_STATUS_PIO_WRITE_OUT_OF_BOUNDS_ERR_SMASK), + /* 5-63 reserved*/ +}; + +/* + * RXE Receive Error flags + */ +#define RXES(name) RCV_ERR_STATUS_RX_##name##_ERR_SMASK +static const struct flag_table rxe_err_status_flags[] = { + /* 0*/ FLAG_ENTRY0("RxDmaCsrCorErr", RXES(DMA_CSR_COR)), + /* 1*/ FLAG_ENTRY0("RxCrkIntfParityErr", RXES(DC_INTF_PARITY)), + /* 2*/ FLAG_ENTRY0("RxRcvHdrUncErr", RXES(RCV_HDR_UNC)), + /* 3*/ FLAG_ENTRY0("RxRcvHdrCorErr", RXES(RCV_HDR_COR)), + /* 4*/ FLAG_ENTRY0("RxRcvDataUncErr", RXES(RCV_DATA_UNC)), + /* 5*/ FLAG_ENTRY0("RxRcvDataCorErr", RXES(RCV_DATA_COR)), + /* 6*/ FLAG_ENTRY0("RxRcvQpMapTableUncErr", RXES(RCV_QP_MAP_TABLE_UNC)), + /* 7*/ FLAG_ENTRY0("RxRcvQpMapTableCorErr", RXES(RCV_QP_MAP_TABLE_COR)), + /* 8*/ FLAG_ENTRY0("RxRcvCsrParityErr", RXES(RCV_CSR_PARITY)), + /* 9*/ FLAG_ENTRY0("RxCrkSopEopParityErr", RXES(DC_SOP_EOP_PARITY)), + /*10*/ FLAG_ENTRY0("RxDmaFlagUncErr", RXES(DMA_FLAG_UNC)), + /*11*/ FLAG_ENTRY0("RxDmaFlagCorErr", RXES(DMA_FLAG_COR)), + /*12*/ FLAG_ENTRY0("RxRcvFsmEncodingErr", RXES(RCV_FSM_ENCODING)), + /*13*/ FLAG_ENTRY0("RxRbufFreeListUncErr", RXES(RBUF_FREE_LIST_UNC)), + /*14*/ FLAG_ENTRY0("RxRbufFreeListCorErr", RXES(RBUF_FREE_LIST_COR)), + /*15*/ + FLAG_ENTRY0("RxRbufLookupDesRegUncErr", RXES(RBUF_LOOKUP_DES_REG_UNC)), + /*16*/ + FLAG_ENTRY0("RxRbufLookupDesRegUncCorErr", + RXES(RBUF_LOOKUP_DES_REG_UNC_COR)), + /*17*/ FLAG_ENTRY0("RxRbufLookupDesUncErr", RXES(RBUF_LOOKUP_DES_UNC)), + /*18*/ FLAG_ENTRY0("RxRbufLookupDesCorErr", RXES(RBUF_LOOKUP_DES_COR)), + /*19*/ + FLAG_ENTRY0("RxRbufBlockListReadUncErr", + RXES(RBUF_BLOCK_LIST_READ_UNC)), + /*20*/ + FLAG_ENTRY0("RxRbufBlockListReadCorErr", + RXES(RBUF_BLOCK_LIST_READ_COR)), + /*21*/ + FLAG_ENTRY0("RxRbufCsrQHeadBufNumParityErr", + RXES(RBUF_CSR_QHEAD_BUF_NUM_PARITY)), + /*22*/ + FLAG_ENTRY0("RxRbufCsrQEntCntParityErr", + RXES(RBUF_CSR_QENT_CNT_PARITY)), + /*23*/ + FLAG_ENTRY0("RxRbufCsrQNextBufParityErr", + RXES(RBUF_CSR_QNEXT_BUF_PARITY)), + /*24*/ + FLAG_ENTRY0("RxRbufCsrQVldBitParityErr", + RXES(RBUF_CSR_QVLD_BIT_PARITY)), + /*25*/ + FLAG_ENTRY0("RxRbufCsrQHdPtrParityErr", RXES(RBUF_CSR_QHD_PTR_PARITY)), + /*26*/ + FLAG_ENTRY0("RxRbufCsrQTlPtrParityErr", RXES(RBUF_CSR_QTL_PTR_PARITY)), + /*27*/ + FLAG_ENTRY0("RxRbufCsrQNumOfPktParityErr", + RXES(RBUF_CSR_QNUM_OF_PKT_PARITY)), + /*28*/ + FLAG_ENTRY0("RxRbufCsrQEOPDWParityErr", RXES(RBUF_CSR_QEOPDW_PARITY)), + /*29*/ FLAG_ENTRY0("RxRbufCtxIdParityErr", RXES(RBUF_CTX_ID_PARITY)), + /*30*/ FLAG_ENTRY0("RxRBufBadLookupErr", RXES(RBUF_BAD_LOOKUP)), + /*31*/ FLAG_ENTRY0("RxRbufFullErr", RXES(RBUF_FULL)), + /*32*/ FLAG_ENTRY0("RxRbufEmptyErr", RXES(RBUF_EMPTY)), + /*33*/ FLAG_ENTRY0("RxRbufFlRdAddrParityErr", RXES(RBUF_FL_RD_ADDR_PARITY)), + /*34*/ FLAG_ENTRY0("RxRbufFlWrAddrParityErr", RXES(RBUF_FL_WR_ADDR_PARITY)), + /*35*/ + FLAG_ENTRY0("RxRbufFlInitdoneParityErr", RXES(RBUF_FL_INITDONE_PARITY)), + /*36*/ + FLAG_ENTRY0("RxRbufFlInitWrAddrParityErr", + RXES(RBUF_FL_INIT_WR_ADDR_PARITY)), + /*37*/ FLAG_ENTRY0("RxRbufNextFreeBufUncErr", RXES(RBUF_NEXT_FREE_BUF_UNC)), + /*38*/ FLAG_ENTRY0("RxRbufNextFreeBufCorErr", RXES(RBUF_NEXT_FREE_BUF_COR)), + /*39*/ FLAG_ENTRY0("RxLookupDesPart1UncErr", RXES(LOOKUP_DES_PART1_UNC)), + /*40*/ + FLAG_ENTRY0("RxLookupDesPart1UncCorErr", + RXES(LOOKUP_DES_PART1_UNC_COR)), + /*41*/ + FLAG_ENTRY0("RxLookupDesPart2ParityErr", RXES(LOOKUP_DES_PART2_PARITY)), + /*42*/ FLAG_ENTRY0("RxLookupRcvArrayUncErr", RXES(LOOKUP_RCV_ARRAY_UNC)), + /*43*/ FLAG_ENTRY0("RxLookupRcvArrayCorErr", RXES(LOOKUP_RCV_ARRAY_COR)), + /*44*/ FLAG_ENTRY0("RxLookupCsrParityErr", RXES(LOOKUP_CSR_PARITY)), + /*45*/ FLAG_ENTRY0("RxHqIntrCsrParityErr", RXES(HQ_INTR_CSR_PARITY)), + /*46*/ FLAG_ENTRY0("RxHqIntrFsmErr", RXES(HQ_INTR_FSM)), + /*47*/ FLAG_ENTRY0("RxRbufDescPart1UncErr", RXES(RBUF_DESC_PART1_UNC)), + /*48*/ FLAG_ENTRY0("RxRbufDescPart1CorErr", RXES(RBUF_DESC_PART1_COR)), + /*49*/ FLAG_ENTRY0("RxRbufDescPart2UncErr", RXES(RBUF_DESC_PART2_UNC)), + /*50*/ FLAG_ENTRY0("RxRbufDescPart2CorErr", RXES(RBUF_DESC_PART2_COR)), + /*51*/ FLAG_ENTRY0("RxDmaHdrFifoRdUncErr", RXES(DMA_HDR_FIFO_RD_UNC)), + /*52*/ FLAG_ENTRY0("RxDmaHdrFifoRdCorErr", RXES(DMA_HDR_FIFO_RD_COR)), + /*53*/ FLAG_ENTRY0("RxDmaDataFifoRdUncErr", RXES(DMA_DATA_FIFO_RD_UNC)), + /*54*/ FLAG_ENTRY0("RxDmaDataFifoRdCorErr", RXES(DMA_DATA_FIFO_RD_COR)), + /*55*/ FLAG_ENTRY0("RxRbufDataUncErr", RXES(RBUF_DATA_UNC)), + /*56*/ FLAG_ENTRY0("RxRbufDataCorErr", RXES(RBUF_DATA_COR)), + /*57*/ FLAG_ENTRY0("RxDmaCsrParityErr", RXES(DMA_CSR_PARITY)), + /*58*/ FLAG_ENTRY0("RxDmaEqFsmEncodingErr", RXES(DMA_EQ_FSM_ENCODING)), + /*59*/ FLAG_ENTRY0("RxDmaDqFsmEncodingErr", RXES(DMA_DQ_FSM_ENCODING)), + /*60*/ FLAG_ENTRY0("RxDmaCsrUncErr", RXES(DMA_CSR_UNC)), + /*61*/ FLAG_ENTRY0("RxCsrReadBadAddrErr", RXES(CSR_READ_BAD_ADDR)), + /*62*/ FLAG_ENTRY0("RxCsrWriteBadAddrErr", RXES(CSR_WRITE_BAD_ADDR)), /* WFR */ + /*63*/ FLAG_ENTRY0("RxCsrParityErr", RXES(CSR_PARITY)) /* WFR */ +}; + +/* RXE errors that will trigger an SPC freeze */ +#define ALL_RXE_FREEZE_ERR \ + (RCV_ERR_STATUS_RX_RCV_QP_MAP_TABLE_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RCV_CSR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_FLAG_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RCV_FSM_ENCODING_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_FREE_LIST_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_LOOKUP_DES_REG_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_LOOKUP_DES_REG_UNC_COR_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_LOOKUP_DES_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_BLOCK_LIST_READ_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CSR_QHEAD_BUF_NUM_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CSR_QENT_CNT_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CSR_QNEXT_BUF_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CSR_QVLD_BIT_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CSR_QHD_PTR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CSR_QTL_PTR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CSR_QNUM_OF_PKT_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CSR_QEOPDW_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_CTX_ID_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_BAD_LOOKUP_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_FULL_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_EMPTY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_FL_RD_ADDR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_FL_WR_ADDR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_FL_INITDONE_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_FL_INIT_WR_ADDR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_NEXT_FREE_BUF_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_LOOKUP_DES_PART1_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_LOOKUP_DES_PART1_UNC_COR_ERR_SMASK | \ + RCV_ERR_STATUS_RX_LOOKUP_DES_PART2_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_LOOKUP_RCV_ARRAY_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_LOOKUP_CSR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_HQ_INTR_CSR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_HQ_INTR_FSM_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_DESC_PART1_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_DESC_PART1_COR_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_DESC_PART2_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_HDR_FIFO_RD_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_DATA_FIFO_RD_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_RBUF_DATA_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_CSR_PARITY_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_EQ_FSM_ENCODING_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_DQ_FSM_ENCODING_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_CSR_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_CSR_PARITY_ERR_SMASK) + +#define RXE_FREEZE_ABORT_MASK \ + (RCV_ERR_STATUS_RX_DMA_CSR_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_HDR_FIFO_RD_UNC_ERR_SMASK | \ + RCV_ERR_STATUS_RX_DMA_DATA_FIFO_RD_UNC_ERR_SMASK) + +/* + * DCC Error Flags + */ +#define DCCE(name) DCC_ERR_FLG_##name##_SMASK +static const struct flag_table dcc_err_flags[] = { + FLAG_ENTRY0("bad_l2_err", DCCE(BAD_L2_ERR)), + FLAG_ENTRY0("bad_sc_err", DCCE(BAD_SC_ERR)), + FLAG_ENTRY0("bad_mid_tail_err", DCCE(BAD_MID_TAIL_ERR)), + FLAG_ENTRY0("bad_preemption_err", DCCE(BAD_PREEMPTION_ERR)), + FLAG_ENTRY0("preemption_err", DCCE(PREEMPTION_ERR)), + FLAG_ENTRY0("preemptionvl15_err", DCCE(PREEMPTIONVL15_ERR)), + FLAG_ENTRY0("bad_vl_marker_err", DCCE(BAD_VL_MARKER_ERR)), + FLAG_ENTRY0("bad_dlid_target_err", DCCE(BAD_DLID_TARGET_ERR)), + FLAG_ENTRY0("bad_lver_err", DCCE(BAD_LVER_ERR)), + FLAG_ENTRY0("uncorrectable_err", DCCE(UNCORRECTABLE_ERR)), + FLAG_ENTRY0("bad_crdt_ack_err", DCCE(BAD_CRDT_ACK_ERR)), + FLAG_ENTRY0("unsup_pkt_type", DCCE(UNSUP_PKT_TYPE)), + FLAG_ENTRY0("bad_ctrl_flit_err", DCCE(BAD_CTRL_FLIT_ERR)), + FLAG_ENTRY0("event_cntr_parity_err", DCCE(EVENT_CNTR_PARITY_ERR)), + FLAG_ENTRY0("event_cntr_rollover_err", DCCE(EVENT_CNTR_ROLLOVER_ERR)), + FLAG_ENTRY0("link_err", DCCE(LINK_ERR)), + FLAG_ENTRY0("misc_cntr_rollover_err", DCCE(MISC_CNTR_ROLLOVER_ERR)), + FLAG_ENTRY0("bad_ctrl_dist_err", DCCE(BAD_CTRL_DIST_ERR)), + FLAG_ENTRY0("bad_tail_dist_err", DCCE(BAD_TAIL_DIST_ERR)), + FLAG_ENTRY0("bad_head_dist_err", DCCE(BAD_HEAD_DIST_ERR)), + FLAG_ENTRY0("nonvl15_state_err", DCCE(NONVL15_STATE_ERR)), + FLAG_ENTRY0("vl15_multi_err", DCCE(VL15_MULTI_ERR)), + FLAG_ENTRY0("bad_pkt_length_err", DCCE(BAD_PKT_LENGTH_ERR)), + FLAG_ENTRY0("unsup_vl_err", DCCE(UNSUP_VL_ERR)), + FLAG_ENTRY0("perm_nvl15_err", DCCE(PERM_NVL15_ERR)), + FLAG_ENTRY0("slid_zero_err", DCCE(SLID_ZERO_ERR)), + FLAG_ENTRY0("dlid_zero_err", DCCE(DLID_ZERO_ERR)), + FLAG_ENTRY0("length_mtu_err", DCCE(LENGTH_MTU_ERR)), + FLAG_ENTRY0("rx_early_drop_err", DCCE(RX_EARLY_DROP_ERR)), + FLAG_ENTRY0("late_short_err", DCCE(LATE_SHORT_ERR)), + FLAG_ENTRY0("late_long_err", DCCE(LATE_LONG_ERR)), + FLAG_ENTRY0("late_ebp_err", DCCE(LATE_EBP_ERR)), + FLAG_ENTRY0("fpe_tx_fifo_ovflw_err", DCCE(FPE_TX_FIFO_OVFLW_ERR)), + FLAG_ENTRY0("fpe_tx_fifo_unflw_err", DCCE(FPE_TX_FIFO_UNFLW_ERR)), + FLAG_ENTRY0("csr_access_blocked_host", DCCE(CSR_ACCESS_BLOCKED_HOST)), + FLAG_ENTRY0("csr_access_blocked_uc", DCCE(CSR_ACCESS_BLOCKED_UC)), + FLAG_ENTRY0("tx_ctrl_parity_err", DCCE(TX_CTRL_PARITY_ERR)), + FLAG_ENTRY0("tx_ctrl_parity_mbe_err", DCCE(TX_CTRL_PARITY_MBE_ERR)), + FLAG_ENTRY0("tx_sc_parity_err", DCCE(TX_SC_PARITY_ERR)), + FLAG_ENTRY0("rx_ctrl_parity_mbe_err", DCCE(RX_CTRL_PARITY_MBE_ERR)), + FLAG_ENTRY0("csr_parity_err", DCCE(CSR_PARITY_ERR)), + FLAG_ENTRY0("csr_inval_addr", DCCE(CSR_INVAL_ADDR)), + FLAG_ENTRY0("tx_byte_shft_parity_err", DCCE(TX_BYTE_SHFT_PARITY_ERR)), + FLAG_ENTRY0("rx_byte_shft_parity_err", DCCE(RX_BYTE_SHFT_PARITY_ERR)), + FLAG_ENTRY0("fmconfig_err", DCCE(FMCONFIG_ERR)), + FLAG_ENTRY0("rcvport_err", DCCE(RCVPORT_ERR)), +}; + +/* + * LCB error flags + */ +#define LCBE(name) DC_LCB_ERR_FLG_##name##_SMASK +static const struct flag_table lcb_err_flags[] = { + /* 0*/ FLAG_ENTRY0("CSR_PARITY_ERR", LCBE(CSR_PARITY_ERR)), + /* 1*/ FLAG_ENTRY0("INVALID_CSR_ADDR", LCBE(INVALID_CSR_ADDR)), + /* 2*/ FLAG_ENTRY0("RST_FOR_FAILED_DESKEW", LCBE(RST_FOR_FAILED_DESKEW)), + /* 3*/ + FLAG_ENTRY0("ALL_LNS_FAILED_REINIT_TEST", + LCBE(ALL_LNS_FAILED_REINIT_TEST)), + /* 4*/ + FLAG_ENTRY0("LOST_REINIT_STALL_OR_TOS", LCBE(LOST_REINIT_STALL_OR_TOS)), + /* 5*/ FLAG_ENTRY0("TX_LESS_THAN_FOUR_LNS", LCBE(TX_LESS_THAN_FOUR_LNS)), + /* 6*/ FLAG_ENTRY0("RX_LESS_THAN_FOUR_LNS", LCBE(RX_LESS_THAN_FOUR_LNS)), + /* 7*/ FLAG_ENTRY0("SEQ_CRC_ERR", LCBE(SEQ_CRC_ERR)), + /* 8*/ FLAG_ENTRY0("REINIT_FROM_PEER", LCBE(REINIT_FROM_PEER)), + /* 9*/ FLAG_ENTRY0("REINIT_FOR_LN_DEGRADE", LCBE(REINIT_FOR_LN_DEGRADE)), + /*10*/ FLAG_ENTRY0("CRC_ERR_CNT_HIT_LIMIT", LCBE(CRC_ERR_CNT_HIT_LIMIT)), + /*11*/ FLAG_ENTRY0("RCLK_STOPPED", LCBE(RCLK_STOPPED)), + /*12*/ + FLAG_ENTRY0("UNEXPECTED_REPLAY_MARKER", LCBE(UNEXPECTED_REPLAY_MARKER)), + /*13*/ + FLAG_ENTRY0("UNEXPECTED_ROUND_TRIP_MARKER", + LCBE(UNEXPECTED_ROUND_TRIP_MARKER)), + /*14*/ FLAG_ENTRY0("ILLEGAL_NULL_LTP", LCBE(ILLEGAL_NULL_LTP)), + /*15*/ FLAG_ENTRY0("ILLEGAL_FLIT_ENCODING", LCBE(ILLEGAL_FLIT_ENCODING)), + /*16*/ FLAG_ENTRY0("FLIT_INPUT_BUF_OFLW", LCBE(FLIT_INPUT_BUF_OFLW)), + /*17*/ FLAG_ENTRY0("VL_ACK_INPUT_BUF_OFLW", LCBE(VL_ACK_INPUT_BUF_OFLW)), + /*18*/ + FLAG_ENTRY0("VL_ACK_INPUT_PARITY_ERR", LCBE(VL_ACK_INPUT_PARITY_ERR)), + /*19*/ + FLAG_ENTRY0("VL_ACK_INPUT_WRONG_CRC_MODE", + LCBE(VL_ACK_INPUT_WRONG_CRC_MODE)), + /*20*/ FLAG_ENTRY0("FLIT_INPUT_BUF_MBE", LCBE(FLIT_INPUT_BUF_MBE)), + /*21*/ FLAG_ENTRY0("FLIT_INPUT_BUF_SBE", LCBE(FLIT_INPUT_BUF_SBE)), + /*22*/ FLAG_ENTRY0("REPLAY_BUF_MBE", LCBE(REPLAY_BUF_MBE)), + /*23*/ FLAG_ENTRY0("REPLAY_BUF_SBE", LCBE(REPLAY_BUF_SBE)), + /*24*/ FLAG_ENTRY0("CREDIT_RETURN_FLIT_MBE", LCBE(CREDIT_RETURN_FLIT_MBE)), + /*25*/ FLAG_ENTRY0("RST_FOR_LINK_TIMEOUT", LCBE(RST_FOR_LINK_TIMEOUT)), + /*26*/ + FLAG_ENTRY0("RST_FOR_INCOMPLT_RND_TRIP", + LCBE(RST_FOR_INCOMPLT_RND_TRIP)), + /*27*/ FLAG_ENTRY0("HOLD_REINIT", LCBE(HOLD_REINIT)), + /*28*/ + FLAG_ENTRY0("NEG_EDGE_LINK_TRANSFER_ACTIVE", + LCBE(NEG_EDGE_LINK_TRANSFER_ACTIVE)), + /*29*/ + FLAG_ENTRY0("REDUNDANT_FLIT_PARITY_ERR", + LCBE(REDUNDANT_FLIT_PARITY_ERR)) +}; + +/* + * DC8051 Error Flags + */ +#define D8E(name) DC_DC8051_ERR_FLG_##name##_SMASK +static const struct flag_table dc8051_err_flags[] = { + FLAG_ENTRY0("SET_BY_8051", D8E(SET_BY_8051)), + FLAG_ENTRY0("LOST_8051_HEART_BEAT", D8E(LOST_8051_HEART_BEAT)), + FLAG_ENTRY0("CRAM_MBE", D8E(CRAM_MBE)), + FLAG_ENTRY0("CRAM_SBE", D8E(CRAM_SBE)), + FLAG_ENTRY0("DRAM_MBE", D8E(DRAM_MBE)), + FLAG_ENTRY0("DRAM_SBE", D8E(DRAM_SBE)), + FLAG_ENTRY0("IRAM_MBE", D8E(IRAM_MBE)), + FLAG_ENTRY0("IRAM_SBE", D8E(IRAM_SBE)), + FLAG_ENTRY0("UNMATCHED_SECURE_MSG_ACROSS_BCC_LANES", + D8E(UNMATCHED_SECURE_MSG_ACROSS_BCC_LANES)), + FLAG_ENTRY0("INVALID_CSR_ADDR", D8E(INVALID_CSR_ADDR)), +}; + +/* + * DC8051 Information Error flags + * + * Flags in DC8051_DBG_ERR_INFO_SET_BY_8051.ERROR field. + */ +static const struct flag_table dc8051_info_err_flags[] = { + FLAG_ENTRY0("Spico ROM check failed", SPICO_ROM_FAILED), + FLAG_ENTRY0("Unknown frame received", UNKNOWN_FRAME), + FLAG_ENTRY0("Target BER not met", TARGET_BER_NOT_MET), + FLAG_ENTRY0("Serdes internal hfi2_loopback failure", + FAILED_SERDES_INTERNAL_LOOPBACK), + FLAG_ENTRY0("Failed SerDes init", FAILED_SERDES_INIT), + FLAG_ENTRY0("Failed LNI(Polling)", FAILED_LNI_POLLING), + FLAG_ENTRY0("Failed LNI(Debounce)", FAILED_LNI_DEBOUNCE), + FLAG_ENTRY0("Failed LNI(EstbComm)", FAILED_LNI_ESTBCOMM), + FLAG_ENTRY0("Failed LNI(OptEq)", FAILED_LNI_OPTEQ), + FLAG_ENTRY0("Failed LNI(VerifyCap_1)", FAILED_LNI_VERIFY_CAP1), + FLAG_ENTRY0("Failed LNI(VerifyCap_2)", FAILED_LNI_VERIFY_CAP2), + FLAG_ENTRY0("Failed LNI(ConfigLT)", FAILED_LNI_CONFIGLT), + FLAG_ENTRY0("Host Handshake Timeout", HOST_HANDSHAKE_TIMEOUT), + FLAG_ENTRY0("External Device Request Timeout", + EXTERNAL_DEVICE_REQ_TIMEOUT), +}; + +/* + * DC8051 Information Host Information flags + * + * Flags in DC8051_DBG_ERR_INFO_SET_BY_8051.HOST_MSG field. + */ +static const struct flag_table dc8051_info_host_msg_flags[] = { + FLAG_ENTRY0("Host request done", 0x0001), + FLAG_ENTRY0("BC PWR_MGM message", 0x0002), + FLAG_ENTRY0("BC SMA message", 0x0004), + FLAG_ENTRY0("BC Unknown message (BCC)", 0x0008), + FLAG_ENTRY0("BC Unknown message (LCB)", 0x0010), + FLAG_ENTRY0("External device config request", 0x0020), + FLAG_ENTRY0("VerifyCap all frames received", 0x0040), + FLAG_ENTRY0("LinkUp achieved", 0x0080), + FLAG_ENTRY0("Link going down", 0x0100), + FLAG_ENTRY0("Link width downgraded", 0x0200), +}; + +static u32 chip_to_opa_lstate(struct hfi2_devdata *dd, u32 chip_lstate); +static int set_physical_link_state(struct hfi2_devdata *dd, u64 state); +static void read_vc_remote_phy(struct hfi2_devdata *dd, u8 *power_management, + u8 *continuous); +static void read_vc_remote_fabric(struct hfi2_devdata *dd, u8 *vau, u8 *z, + u8 *vcu, u16 *vl15buf, u8 *crc_sizes); +static void read_vc_remote_link_width(struct hfi2_devdata *dd, + u8 *remote_tx_rate, u16 *link_widths); +static void read_vc_local_link_mode(struct hfi2_devdata *dd, u8 *misc_bits, + u8 *flag_bits, u16 *link_widths); +static void read_remote_device_id(struct hfi2_devdata *dd, u16 *device_id, + u8 *device_rev); +static void read_local_lni(struct hfi2_devdata *dd, u8 *enable_lane_rx); +static int read_tx_settings(struct hfi2_devdata *dd, u8 *enable_lane_tx, + u8 *tx_polarity_inversion, + u8 *rx_polarity_inversion, u8 *max_rate); +static void handle_qsfp_int(struct hfi2_devdata *dd, u32 source, u64 reg); +static void handle_dcc_err(struct hfi2_devdata *dd, unsigned int context, + u64 err_status); +static void handle_lcb_err(struct hfi2_devdata *dd, unsigned int context, + u64 err_status); +static void handle_8051_interrupt(struct hfi2_devdata *dd, u32 unused, u64 reg); +static void handle_cce_err(struct hfi2_devdata *dd, u32 unused, u64 reg); +static void handle_misc_err(struct hfi2_devdata *dd, u32 unused, u64 reg); +static void handle_txe_err(struct hfi2_devdata *dd, u32 unused, u64 reg); +static void set_partition_keys(struct hfi2_pportdata *ppd); +static int do_8051_command(struct hfi2_devdata *dd, u32 type, u64 in_data, + u64 *out_data); +static int read_idle_sma(struct hfi2_devdata *dd, u64 *data); +static int thermal_init(struct hfi2_devdata *dd); + +static int wait_phys_link_offline_substates(struct hfi2_pportdata *ppd, + int msecs); +static int wait_logical_linkstate(struct hfi2_pportdata *ppd, u32 state, + int msecs); +static void log_physical_state(struct hfi2_pportdata *ppd, u32 state); +static int wait_physical_linkstate(struct hfi2_pportdata *ppd, u32 state, + int msecs); +static int wait_phys_link_out_of_offline(struct hfi2_pportdata *ppd, int msecs); +static void read_planned_down_reason_code(struct hfi2_devdata *dd, u8 *pdrrc); +static void read_link_down_reason(struct hfi2_devdata *dd, u8 *ldr); +static void dc_shutdown(struct hfi2_devdata *dd); +static void dc_start(struct hfi2_devdata *dd); +static int qos_rmt_entries(unsigned int n_krcv_queues, unsigned int *mp, + unsigned int *np); +static void clear_full_mgmt_pkey(struct hfi2_pportdata *ppd); +static int wait_link_transfer_active(struct hfi2_devdata *dd, int wait_ms); +static void clear_rsm_rule(struct hfi2_devdata *dd, int rule_index); + +#define NUM_MISC_ERRS (IS_GENERAL_ERR_END + 1 - IS_GENERAL_ERR_START) +#define NUM_DC_ERRS (IS_DC_END + 1 - IS_DC_START) +#define NUM_VARIOUS (IS_VARIOUS_END + 1 - IS_VARIOUS_START) + +/* + * Helpers DC error interrupt table entries. Different helpers are needed + * because of inconsistent register names. + */ +#define DC_EE1(reg, handler, desc) \ + { \ + reg##_FLG, reg##_FLG_CLR, reg##_FLG_EN, ICD_NORMAL, handler, \ + desc \ + } +#define DC_EE2(reg, handler, desc) \ + { \ + reg##_FLG, reg##_CLR, reg##_EN, ICD_NORMAL, handler, desc \ + } + +/* + * Table of the "misc" grouping of error interrupts. Each entry refers to + * another register containing more information. + */ +static const struct err_reg_info misc_errs[NUM_MISC_ERRS] = { + EE_N(CCE_ERR, handle_cce_err, "CceErr"), + EE_I(WFR_RCV_ERR, hfi2_handle_rxe_err, "RxeErr"), + EE_N(MISC_ERR, handle_misc_err, "MiscErr"), + {}, /* reserved */ + EE_N(WFR_SEND_PIO_ERR, hfi2_handle_pio_err, "PioErr"), + EE_N(WFR_SEND_DMA_ERR, hfi2_handle_sdma_err, "SDmaErr"), + EE_E(WFR_SEND_EGRESS_ERR, hfi2_handle_egress_err, "EgressErr"), + EE_N(WFR_SEND_ERR, handle_txe_err, "TxeErr") + /* the rest are reserved */ +}; + +/* + * Index into the Various section of the interrupt sources + * corresponding to the Critical Temperature interrupt. + */ +#define TCRIT_INT_SOURCE 4 + +/* + * SDMA error interrupt entry - refers to another register containing more + * information. + */ +static const struct err_reg_info sdma_eng_err = + EE_S(WFR_SEND_DMA_ENG_ERR, hfi2_handle_sdma_eng_err, "SDmaEngErr"); + +static const struct err_reg_info various_err[NUM_VARIOUS] = { + {}, /* PbcInt */ + {}, /* GpioAssertInt */ + EE_N(ASIC_QSFP1, handle_qsfp_int, "QSFP1"), + EE_N(ASIC_QSFP2, handle_qsfp_int, "QSFP2"), + {}, /* TCritInt */ + /* the rest are reserved */ +}; + +/* + * The DC encoding of mtu_cap for 10K MTU in the DCC_CFG_PORT_CONFIG + * register can not be derived from the MTU value because 10K is not + * a power of 2. Therefore, we need a constant. Everything else can + * be calculated. + */ +#define DCC_CFG_PORT_MTU_CAP_10240 7 + +/* + * Table of the DC grouping of error interrupts. Each entry refers to + * another register containing more information. + */ +static const struct err_reg_info dc_errs[NUM_DC_ERRS] = { + DC_EE1(DCC_ERR, handle_dcc_err, "DCC Err"), + DC_EE2(DC_LCB_ERR, handle_lcb_err, "LCB Err"), + DC_EE2(DC_DC8051_ERR, handle_8051_interrupt, "DC8051 Interrupt"), + /* dc_lbm_int - special, see is_dc_int() */ + /* the rest are reserved */ +}; + +/* ======================================================================== */ + +/* return true if this is WFR chip revision a */ +int hfi2_is_ax(struct hfi2_devdata *dd) +{ + int chip_rev_minor; + + if (dd->params->chip_type != CHIP_WFR) + return 0; + + chip_rev_minor = (dd->revision >> CCE_REVISION_CHIP_REV_MINOR_SHIFT) & + CCE_REVISION_CHIP_REV_MINOR_MASK; + return (chip_rev_minor & 0xf0) == 0; +} + +/* return true if this is WFR chip revision b or not WFR */ +int hfi2_is_bx(struct hfi2_devdata *dd) +{ + int chip_rev_minor; + + if (dd->params->chip_type != CHIP_WFR) + return 1; + + chip_rev_minor = (dd->revision >> CCE_REVISION_CHIP_REV_MINOR_SHIFT) & + CCE_REVISION_CHIP_REV_MINOR_MASK; + return (chip_rev_minor & 0xF0) == 0x10; +} + +/* return true is kernel urg disabled for rcd */ +bool hfi2_is_urg_masked(struct hfi2_ctxtdata *rcd) +{ + u64 mask; + u32 is = rcd->dd->params->is_rcvurgent_start + rcd->ctxt; + u8 bit = is % 64; + + mask = hfi2_read_csr(rcd->dd, rcd->dd->params->cce_int_mask_reg + + (8 * (is / 64))); + return !(mask & BIT_ULL(bit)); +} + +/* + * Append string s to buffer buf. Arguments curp and len are the current + * position and remaining length, respectively. + * + * return 0 on success, 1 on out of room + */ +static int append_str(char *buf, char **curp, int *lenp, const char *s) +{ + char *p = *curp; + int len = *lenp; + int result = 0; /* success */ + char c; + + /* add a comma, if first in the buffer */ + if (p != buf) { + if (len == 0) { + result = 1; /* out of room */ + goto done; + } + *p++ = ','; + len--; + } + + /* copy the string */ + while ((c = *s++) != 0) { + if (len == 0) { + result = 1; /* out of room */ + goto done; + } + *p++ = c; + len--; + } + +done: + /* write return values */ + *curp = p; + *lenp = len; + + return result; +} + +/* + * Using the given flag table, print a comma separated string into + * the buffer. End in '*' if the buffer is too short. + */ +static char *flag_string(char *buf, int buf_len, u64 flags, + const struct flag_table *table, int table_size) +{ + char extra[32]; + char *p = buf; + int len = buf_len; + int no_room = 0; + int i; + + /* make sure there is at least 2 so we can form "*" */ + if (len < 2) + return ""; + + len--; /* leave room for a nul */ + for (i = 0; i < table_size; i++) { + if (flags & table[i].flag) { + no_room = append_str(buf, &p, &len, table[i].str); + if (no_room) + break; + flags &= ~table[i].flag; + } + } + + /* any undocumented bits left? */ + if (!no_room && flags) { + snprintf(extra, sizeof(extra), "bits 0x%llx", flags); + no_room = append_str(buf, &p, &len, extra); + } + + /* add * if ran out of room */ + if (no_room) { + /* may need to back up to add space for a '*' */ + if (len == 0) + --p; + *p++ = '*'; + } + + /* add final nul - space already allocated above */ + *p = 0; + return buf; +} + +/* first 8 CCE error interrupt source names */ +static const char *const cce_misc_names[] = { + "CceErrInt", /* 0 */ + "RxeErrInt", /* 1 */ + "MiscErrInt", /* 2 */ + "Reserved3", /* 3 */ + "PioErrInt", /* 4 */ + "SDmaErrInt", /* 5 */ + "EgressErrInt", /* 6 */ + "TxeErrInt" /* 7 */ +}; + +/* + * Return the miscellaneous error interrupt name. + */ +static char *is_misc_err_name(char *buf, size_t bsize, unsigned int source) +{ + if (source < ARRAY_SIZE(cce_misc_names)) + strscpy_pad(buf, cce_misc_names[source], bsize); + else + snprintf(buf, bsize, "Reserved%u", + source + IS_GENERAL_ERR_START); + + return buf; +} + +/* + * Return the SDMA engine error interrupt name. + */ +char *hfi2_is_sdma_eng_err_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "SDmaEngErrInt%u", source); + return buf; +} + +/* + * Return the send context error interrupt name. + */ +char *hfi2_is_sendctxt_err_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "SendCtxtErrInt%u", source); + return buf; +} + +static const char *const various_names[] = { "PbcInt", "GpioAssertInt", + "Qsfp1Int", "Qsfp2Int", + "TCritInt" }; + +/* + * Return the various interrupt name. + */ +static char *is_various_name(char *buf, size_t bsize, unsigned int source) +{ + if (source < ARRAY_SIZE(various_names)) + strscpy_pad(buf, various_names[source], bsize); + else + snprintf(buf, bsize, "Reserved%u", source + IS_VARIOUS_START); + return buf; +} + +/* + * Return the DC interrupt name. + */ +static char *is_dc_name(char *buf, size_t bsize, unsigned int source) +{ + static const char *const dc_int_names[] = { + "common", "lcb", "8051", "lbm" /* local block merge */ + }; + + if (source < ARRAY_SIZE(dc_int_names)) + snprintf(buf, bsize, "dc_%s_int", dc_int_names[source]); + else + snprintf(buf, bsize, "DCInt%u", source); + return buf; +} + +static const char *const sdma_int_names[] = { + "SDmaInt", + "SdmaIdleInt", + "SdmaProgressInt", +}; + +/* + * Return the SDMA engine interrupt name. + */ +char *hfi2_is_sdma_eng_name(char *buf, size_t bsize, unsigned int source) +{ + /* what interrupt */ + unsigned int what = source / TXE_NUM_SDMA_ENGINES; + /* which engine */ + unsigned int which = source % TXE_NUM_SDMA_ENGINES; + + if (likely(what < 3)) + snprintf(buf, bsize, "%s%u", sdma_int_names[what], which); + else + snprintf(buf, bsize, "Invalid SDMA interrupt %u", source); + return buf; +} + +/* + * Return the receive available interrupt name. + */ +char *hfi2_is_rcv_avail_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "RcvAvailInt%u", source); + return buf; +} + +/* + * Return the receive urgent interrupt name. + */ +char *hfi2_is_rcv_urgent_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "RcvUrgentInt%u", source); + return buf; +} + +/* + * Return the send credit interrupt name. + */ +char *hfi2_is_send_credit_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "SendCreditInt%u", source); + return buf; +} + +/* + * Return the reserved interrupt name. + */ +static char *is_reserved_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "Reserved%u", source + IS_RESERVED_START); + return buf; +} + +static char *cce_err_status_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, cce_err_status_flags, + ARRAY_SIZE(cce_err_status_flags)); +} + +static char *rxe_err_status_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, rxe_err_status_flags, + ARRAY_SIZE(rxe_err_status_flags)); +} + +static char *misc_err_status_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, misc_err_status_flags, + ARRAY_SIZE(misc_err_status_flags)); +} + +static char *pio_err_status_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, pio_err_status_flags, + ARRAY_SIZE(pio_err_status_flags)); +} + +static char *sdma_err_status_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, sdma_err_status_flags, + ARRAY_SIZE(sdma_err_status_flags)); +} + +static char *egress_err_status_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, egress_err_status_flags, + ARRAY_SIZE(egress_err_status_flags)); +} + +static char *egress_err_info_string(struct hfi2_devdata *dd, char *buf, + int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, + dd->params->egress_err_info_data->table, + dd->params->egress_err_info_data->size); +} + +static char *send_err_status_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, send_err_status_flags, + ARRAY_SIZE(send_err_status_flags)); +} + +static void handle_cce_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + char buf[96]; + int i = 0; + + /* + * For most these errors, there is nothing that can be done except + * report or record it. + */ + dd_dev_info(dd, "CCE Error: %s\n", + cce_err_status_string(buf, sizeof(buf), reg)); + + if ((reg & CCE_ERR_STATUS_CCE_CLI2_ASYNC_FIFO_PARITY_ERR_SMASK) && + hfi2_is_ax(dd)) { + /* this error requires a manual drop into SPC freeze mode */ + /* then a fix up */ + hfi2_start_freeze_handling(dd, FREEZE_SELF); + } + + for (i = 0; i < NUM_CCE_ERR_STATUS_COUNTERS; i++) { + if (reg & (1ull << i)) { + incr_cntr64(&dd->cce_err_status_cnt[i]); + /* maintain a counter over all cce_err_status errors */ + incr_cntr64(&dd->sw_cce_err_status_aggregate); + } + } +} + +/* + * Check counters for receive errors that do not have an interrupt + * associated with them. + */ +static void do_rcverr_timer(struct work_struct *work) +{ + struct hfi2_devdata *dd = + container_of(work, struct hfi2_devdata, rcverr_work); + struct hfi2_pportdata *ppd; + u32 cur_ovfl_cnt; + int pidx; + + for (pidx = 0; pidx < dd->num_pports; pidx++) { + ppd = &dd->pport[pidx]; + cur_ovfl_cnt = + hfi2_read_port_cntr(ppd, C_RCV_OVF, CNTR_INVALID_VL); + + if (ppd->rcv_ovfl_cnt < cur_ovfl_cnt && + ppd->port_error_action & OPA_PI_MASK_EX_BUFFER_OVERRUN) { + ppd_dev_info(ppd, "%s: PortErrorAction bounce\n", + __func__); + hfi2_set_link_down_reason( + ppd, + OPA_LINKDOWN_REASON_EXCESSIVE_BUFFER_OVERRUN, 0, + OPA_LINKDOWN_REASON_EXCESSIVE_BUFFER_OVERRUN); + queue_work(ppd->link_wq, &ppd->link_bounce_work); + } + ppd->rcv_ovfl_cnt = cur_ovfl_cnt; + } +} + +#define RCVERR_CHECK_TIME 10 +static void update_rcverr_timer(struct timer_list *t) +{ + struct hfi2_devdata *dd = timer_container_of(dd, t, rcverr_timer); + + /* avoid timer interrupt context CSR access when simulating */ + if (dd->icode == ICODE_FUNCTIONAL_SIMULATOR) + queue_work(dd->update_cntr_wq, &dd->rcverr_work); + else + do_rcverr_timer(&dd->rcverr_work); + mod_timer(&dd->rcverr_timer, jiffies + HZ * RCVERR_CHECK_TIME); +} + +void hfi2_handle_rxe_err(struct hfi2_devdata *dd, u32 pidx, u64 reg) +{ + struct hfi2_pportdata *ppd = &dd->pport[pidx]; + char buf[96]; + int i = 0; + + ppd_dev_info(ppd, "Receive Error: %s\n", + rxe_err_status_string(buf, sizeof(buf), reg)); + + if (reg & ALL_RXE_FREEZE_ERR) { + int flags = 0; + + /* + * Freeze mode recovery is disabled for the errors + * in RXE_FREEZE_ABORT_MASK + */ + if (hfi2_is_ax(dd) && (reg & RXE_FREEZE_ABORT_MASK)) + flags = FREEZE_ABORT; + + hfi2_start_freeze_handling(dd, flags); + } + + for (i = 0; i < NUM_RCV_ERR_STATUS_COUNTERS; i++) { + if (reg & (1ull << i)) + incr_cntr64(&dd->rcv_err_status_cnt[i]); + } +} + +static void handle_misc_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + char buf[96]; + int i = 0; + + dd_dev_info(dd, "Misc Error: %s", + misc_err_status_string(buf, sizeof(buf), reg)); + for (i = 0; i < NUM_MISC_ERR_STATUS_COUNTERS; i++) { + if (reg & (1ull << i)) + incr_cntr64(&dd->misc_err_status_cnt[i]); + } +} + +void hfi2_handle_pio_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + char buf[96]; + int i = 0; + + dd_dev_info(dd, "PIO Error: %s\n", + pio_err_status_string(buf, sizeof(buf), reg)); + + if (reg & ALL_PIO_FREEZE_ERR) + hfi2_start_freeze_handling(dd, 0); + + for (i = 0; i < NUM_SEND_PIO_ERR_STATUS_COUNTERS; i++) { + if (reg & (1ull << i)) + incr_cntr64(&dd->send_pio_err_status_cnt[i]); + } +} + +void hfi2_handle_sdma_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + char buf[96]; + int i = 0; + + dd_dev_info(dd, "SDMA Error: %s\n", + sdma_err_status_string(buf, sizeof(buf), reg)); + + if (reg & ALL_SDMA_FREEZE_ERR) + hfi2_start_freeze_handling(dd, 0); + + for (i = 0; i < NUM_SEND_DMA_ERR_STATUS_COUNTERS; i++) { + if (reg & (1ull << i)) + incr_cntr64(&dd->send_dma_err_status_cnt[i]); + } +} + +static inline void __count_port_discards(struct hfi2_pportdata *ppd) +{ + incr_cntr64(&ppd->port_xmit_discards); +} + +static void count_port_inactive(struct hfi2_pportdata *ppd) +{ + __count_port_discards(ppd); +} + +static u64 egress_err_info(struct hfi2_pportdata *ppd, bool hfi2_loopback) +{ + struct hfi2_devdata *dd = ppd->dd; + u8 pidx = hfi2_loopback ? loopback_pidx(ppd) : ppd->hw_pidx; + + /* read err source first */ + u64 src = read_eport_csr(dd, pidx, + dd->params->send_egress_err_source_reg); + u64 info = + read_eport_csr(dd, pidx, dd->params->send_egress_err_info_reg); + char buf[96]; + + /* clear down all observed info as quickly as possible after read */ + write_eport_csr(dd, pidx, dd->params->send_egress_err_info_reg, info); + + ppd_dev_info( + ppd, + "%s Egress Error Info: 0x%llx, %s Egress Error Src 0x%llx\n", + hfi2_loopback ? "LB" : "FC", info, + egress_err_info_string(dd, buf, sizeof(buf), info), src); + return info; +} + +/* + * We have had a "disallowed packet" error during egress. Determine the + * integrity check which failed, and update relevant error counter, etc. + * + * Note that the SEND_EGRESS_ERR_INFO register has only a single + * bit of state per integrity check, and so we can miss the reason for an + * egress error if more than one packet fails the same integrity check + * since we cleared the corresponding bit in SEND_EGRESS_ERR_INFO. + */ +static void handle_send_egress_err_info(struct hfi2_pportdata *ppd, int vl) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 info = egress_err_info(ppd, false); + + if (dd->is_sriov) + info |= egress_err_info(ppd, true); + + /* Eventually add other counters for each bit */ + if (info & dd->params->port_discard_egress_errs) { + int weight, i; + + /* + * Count all applicable bits as individual errors and + * attribute them to the packet that triggered this handler. + * This may not be completely accurate due to limitations + * on the available hardware error information. There is + * a single information register and any number of error + * packets may have occurred and contributed to it before + * this routine is called. This means that: + * a) If multiple packets with the same error occur before + * this routine is called, earlier packets are missed. + * There is only a single bit for each error type. + * b) Errors may not be attributed to the correct VL. + * The driver is attributing all bits in the info register + * to the packet that triggered this call, but bits + * could be an accumulation of different packets with + * different VLs. + * c) A single error packet may have multiple counts attached + * to it. There is no way for the driver to know if + * multiple bits set in the info register are due to a + * single packet or multiple packets. The driver assumes + * multiple packets. + */ + weight = hweight64(info & dd->params->port_discard_egress_errs); + for (i = 0; i < weight; i++) { + __count_port_discards(ppd); + if (vl >= 0 && vl < TXE_NUM_DATA_VL) + incr_cntr64(&ppd->port_xmit_discards_vl[vl]); + else if (vl == 15) + incr_cntr64( + &ppd->port_xmit_discards_vl[C_VL_15]); + } + } +} + +/* + * Input value is a bit position within the SEND_EGRESS_ERR_STATUS + * register. Does it represent a 'port inactive' error? + */ +static inline int port_inactive_err(int posn) +{ + return (posn >= ilog2(SEES(TX_LINKDOWN)) && + posn <= ilog2(SEES(TX_INCORRECT_LINK_STATE))); +} + +/* + * Input value is a bit position within the SEND_EGRESS_ERR_STATUS + * register. Does it represent a 'disallowed packet' error? + */ +static inline int disallowed_pkt_err(int posn) +{ + return (posn >= ilog2(SEES(TX_SDMA0_DISALLOWED_PACKET)) && + posn <= ilog2(SEES(TX_SDMA15_DISALLOWED_PACKET))); +} + +/* + * Input value is a bit position of one of the SDMA engine disallowed + * packet errors. Return which engine. Use of this must be guarded by + * disallowed_pkt_err(). + */ +static inline int disallowed_pkt_engine(int posn) +{ + return posn - ilog2(SEES(TX_SDMA0_DISALLOWED_PACKET)); +} + +/* + * Translate an SDMA engine to a VL. Return -1 if the tranlation cannot + * be done. + */ +static int engine_to_vl(struct hfi2_pportdata *ppd, int engine) +{ + struct sdma_vl_map *m; + int vl; + + /* range check */ + if (engine < 0 || engine >= TXE_NUM_SDMA_ENGINES) + return -1; + + rcu_read_lock(); + m = rcu_dereference(ppd->sdma_map); + vl = m->engine_to_vl[engine]; + rcu_read_unlock(); + + return vl; +} + +/* + * Translate the send context (sofware index) into a VL. Return -1 if the + * translation cannot be done. + */ +static int sc_to_vl(struct hfi2_devdata *dd, int sw_index) +{ + struct send_context_info *sci; + struct send_context *sc; + int i; + + sci = &dd->send_contexts[sw_index]; + + /* there is no information for user (PSM) and ack contexts */ + if ((sci->type != SC_KERNEL) && (sci->type != SC_VL15)) + return -1; + + sc = sci->sc; + if (!sc) + return -1; + if (sc->ppd->vld[15].sc == sc) + return 15; + for (i = 0; i < hfi2_num_vls; i++) + if (sc->ppd->vld[i].sc == sc) + return i; + + return -1; +} + +void hfi2_handle_egress_err(struct hfi2_devdata *dd, u32 pidx, u64 reg) +{ + struct hfi2_pportdata *ppd = &dd->pport[pidx]; + u64 reg_copy = reg; + char buf[96]; + bool err_info_avail = true; /* info available on first call only */ + + if (reg & ALL_TXE_EGRESS_FREEZE_ERR) + hfi2_start_freeze_handling(dd, 0); + else if (hfi2_is_ax(dd) && + (reg & SEND_EGRESS_ERR_STATUS_TX_CREDIT_RETURN_VL_ERR_SMASK)) + hfi2_start_freeze_handling(dd, 0); + + while (reg_copy) { + int posn = fls64(reg_copy); + /* fls64() returns a 1-based offset, we want it zero based */ + int shift = posn - 1; + u64 mask = 1ULL << shift; + + if (port_inactive_err(shift)) { + count_port_inactive(ppd); + } else if (err_info_avail && disallowed_pkt_err(shift)) { + int vl = + engine_to_vl(ppd, disallowed_pkt_engine(shift)); + + handle_send_egress_err_info(ppd, vl); + err_info_avail = false; + } + incr_cntr64(&dd->send_egress_err_status_cnt[shift]); + reg_copy &= ~mask; + } + + if (reg) + ppd_dev_info(ppd, "Egress Error: %s\n", + egress_err_status_string(buf, sizeof(buf), reg)); +} + +static void handle_txe_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + char buf[96]; + int i = 0; + + dd_dev_info(dd, "Send Error: %s\n", + send_err_status_string(buf, sizeof(buf), reg)); + + for (i = 0; i < NUM_SEND_ERR_STATUS_COUNTERS; i++) { + if (reg & (1ull << i)) + incr_cntr64(&dd->send_err_status_cnt[i]); + } +} + +/* + * The maximum number of times the error clear down will loop before + * blocking a repeating error. This value is arbitrary. + */ +#define MAX_CLEAR_COUNT 20 + +/* helper for hfi2_interrupt_clear_down() register read */ +static u64 read_icd_csr(struct hfi2_devdata *dd, enum icd_type type, u32 idx, + u32 reg) +{ + switch (type) { + case ICD_NORMAL: + return hfi2_read_csr(dd, reg); + case ICD_SDMA: + return read_sdma_csr(dd, idx, reg); + case ICD_INGRESS: + return read_iport_csr(dd, idx, reg); + case ICD_EGRESS: + return read_eport_csr(dd, idx, reg); + } + return 0; +} + +/* helper for hfi2_interrupt_clear_down() register write */ +static void write_icd_csr(struct hfi2_devdata *dd, enum icd_type type, u32 idx, + u32 reg, u64 value) +{ + switch (type) { + case ICD_NORMAL: + hfi2_write_csr(dd, reg, value); + return; + case ICD_SDMA: + write_sdma_csr(dd, idx, reg, value); + return; + case ICD_INGRESS: + write_iport_csr(dd, idx, reg, value); + return; + case ICD_EGRESS: + write_eport_csr(dd, idx, reg, value); + return; + } +} + +/* + * Clear and handle an error register. All error interrupts are funneled + * through here to have a central location to correctly handle single- + * or multi-shot errors. + * + * The error register info indicates the type of access needed for the error + * register. The idx is an indexer for the error register. + * + * If the handler loops too many times, assume that something is wrong + * and can't be fixed, so mask the error bits. + */ +void hfi2_interrupt_clear_down(struct hfi2_devdata *dd, u32 idx, + const struct err_reg_info *eri) +{ + u64 reg; + u32 count; + + /* read in a loop until no more errors are seen */ + count = 0; + while (1) { + reg = read_icd_csr(dd, eri->type, idx, eri->status); + if (reg == 0) + break; + write_icd_csr(dd, eri->type, idx, eri->clear, reg); + if (likely(eri->handler)) + eri->handler(dd, idx, reg); + count++; + if (count > MAX_CLEAR_COUNT) { + u64 mask; + + dd_dev_err(dd, "Repeating %s bits 0x%llx - masking\n", + eri->desc, reg); + /* + * Read-modify-write so any other masked bits + * remain masked. + */ + mask = read_icd_csr(dd, eri->type, idx, eri->mask); + mask &= ~reg; + write_icd_csr(dd, eri->type, idx, eri->mask, mask); + break; + } + } +} + +/* + * CCE block "misc" interrupt. Source is < 16. + */ +static void is_misc_err_int(struct hfi2_devdata *dd, unsigned int source) +{ + const struct err_reg_info *eri = &misc_errs[source]; + + if (eri->handler) { + hfi2_interrupt_clear_down(dd, 0, eri); + } else { + dd_dev_err(dd, "Unexpected misc interrupt (%u) - reserved\n", + source); + } +} + +static char *send_context_err_status_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, sc_err_status_flags, + ARRAY_SIZE(sc_err_status_flags)); +} + +/* + * Send context error interrupt. + * + * All send context errors cause the send context to halt. The normal + * clear-down mechanism cannot be used because we cannot clear the + * error bits until several other long-running items are done first. + * This is OK because with the context halted, nothing else is going + * to happen on it anyway. + */ +void hfi2_is_sendctxt_err_int(struct hfi2_devdata *dd, unsigned int hw_context) +{ + struct send_context_info *sci; + struct send_context *sc; + char flags[96]; + u64 status; + u32 sw_index; + int i = 0; + unsigned long irq_flags; + + sw_index = dd->hw_to_sw[hw_context]; + if (sw_index >= dd->num_send_contexts) { + dd_dev_err(dd, "out of range sw index %u for send context %u\n", + sw_index, hw_context); + return; + } + sci = &dd->send_contexts[sw_index]; + spin_lock_irqsave(&dd->sc_lock, irq_flags); + sc = sci->sc; + if (!sc) { + dd_dev_err(dd, "%s: context %u(%u): no sc?\n", __func__, + sw_index, hw_context); + spin_unlock_irqrestore(&dd->sc_lock, irq_flags); + return; + } + + /* tell the software that a halt has begun */ + hfi2_sc_stop(sc, SCF_HALTED); + + /* + * All per-send context errors will halt the context and no more + * errors can be generated. Calling hfi2_interrupt_clear_down() to handle + * repeating errors is not needed. The per-context error status + * register is cleared when the context is re-enabled. + */ + status = read_sctxt_csr(dd, hw_context, + dd->params->send_ctxt_err_status_reg); + + dd_dev_info(dd, "Send Context %u(%u) Error: %s\n", sw_index, hw_context, + send_context_err_status_string(flags, sizeof(flags), + status)); + + if (status & SEND_CTXT_ERR_STATUS_PIO_DISALLOWED_PACKET_ERR_SMASK) + handle_send_egress_err_info(sc->ppd, sc_to_vl(dd, sw_index)); + + /* + * Automatically restart halted kernel contexts out of interrupt + * context. User contexts must ask the driver to restart the context. + */ + if (sc->type != SC_USER) + queue_work(dd->hfi2_wq, &sc->halt_work); + spin_unlock_irqrestore(&dd->sc_lock, irq_flags); + + /* + * Update the counters for the corresponding status bits. + * Note that these particular counters are aggregated over all + * contexts. + */ + for (i = 0; i < NUM_SEND_CTXT_ERR_STATUS_COUNTERS; i++) { + if (status & (1ull << i)) + incr_cntr64(&dd->sw_ctxt_err_status_cnt[i]); + } +} + +void hfi2_handle_sdma_eng_err(struct hfi2_devdata *dd, unsigned int source, + u64 status) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + struct sdma_engine *sde; + int i = 0; + + if (source < dr->first_sdma_engine || source >= dr->last_sdma_engine) { + dd_dev_err(dd, "%s: engine %u out of range\n", __func__, + source); + return; + } + sde = &dd->per_sdma[source]; +#ifdef CONFIG_HFI2_SDMA_VERBOSITY + dd_dev_err(sde->dd, "CONFIG SDMA(%u) %s:%d %s()\n", sde->this_idx, + slashstrip(__FILE__), __LINE__, __func__); + dd_dev_err(sde->dd, "CONFIG SDMA(%u) source: %u status 0x%llx\n", + sde->this_idx, source, (unsigned long long)status); +#endif + sde->err_cnt++; + hfi2_sdma_engine_error(sde, status); + + /* + * Update the counters for the corresponding status bits. + * Note that these particular counters are aggregated over + * all 16 DMA engines. + */ + for (i = 0; i < NUM_SEND_DMA_ENG_ERR_STATUS_COUNTERS; i++) { + if (status & (1ull << i)) + incr_cntr64(&dd->sw_send_dma_eng_err_status_cnt[i]); + } +} + +/* + * CCE block SDMA error interrupt. Source is < 16. + */ +static void is_sdma_eng_err_int(struct hfi2_devdata *dd, unsigned int source) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + + if (source < dr->first_sdma_engine || source >= dr->last_sdma_engine) { + dd_dev_err(dd, "%s: engine %u out of range\n", __func__, + source); + goto clear_down; /* must not access sde, but must clear intr */ + } +#ifdef CONFIG_HFI2_SDMA_VERBOSITY + struct sdma_engine *sde = &dd->per_sdma[source]; + + dd_dev_err(dd, "CONFIG SDMA(%u) %s:%d %s()\n", sde->this_idx, + slashstrip(__FILE__), __LINE__, __func__); + dd_dev_err(dd, "CONFIG SDMA(%u) source: %u\n", sde->this_idx, source); + sdma_dumpstate(sde); +#endif +clear_down: + hfi2_interrupt_clear_down(dd, source, &sdma_eng_err); +} + +/* + * CCE block "various" interrupt. Source is < 8. + */ +static void is_various_int(struct hfi2_devdata *dd, unsigned int source) +{ + const struct err_reg_info *eri = &various_err[source]; + + /* + * TCritInt cannot go through hfi2_interrupt_clear_down() + * because it is not a second tier interrupt. The handler + * should be called directly. + */ + if (source == TCRIT_INT_SOURCE) + hfi2_handle_temp_err(dd); + else if (eri->handler) + hfi2_interrupt_clear_down(dd, 0, eri); + else + dd_dev_info(dd, "%s: Unimplemented/reserved interrupt %d\n", + __func__, source); +} + +static void handle_qsfp_int(struct hfi2_devdata *dd, u32 src_ctx, u64 reg) +{ + /* src_ctx is always zero */ + struct hfi2_pportdata *ppd = dd->pport; + unsigned long flags; + u64 qsfp_int_mgmt = (u64)(QSFP_HFI0_INT_N | QSFP_HFI0_MODPRST_N); + + if (reg & QSFP_HFI0_MODPRST_N) { + if (!hfi2_qsfp_mod_present(ppd)) { + ppd_dev_info(ppd, "%s: QSFP module removed\n", + __func__); + + ppd->driver_link_ready = 0; + /* + * Cable removed, reset all our information about the + * cache and cable capabilities + */ + + spin_lock_irqsave(&ppd->qsfp_info.qsfp_lock, flags); + /* + * We don't set cache_refresh_required here as we expect + * an interrupt when a cable is inserted + */ + ppd->qsfp_info.cache_valid = 0; + ppd->qsfp_info.reset_needed = 0; + ppd->qsfp_info.limiting_active = 0; + spin_unlock_irqrestore(&ppd->qsfp_info.qsfp_lock, + flags); + /* Invert the ModPresent pin now to detect plug-in */ + hfi2_write_csr(dd, + dd->hfi2_id ? ASIC_QSFP2_INVERT : + ASIC_QSFP1_INVERT, + qsfp_int_mgmt); + + if ((ppd->offline_disabled_reason > + HFI2_ODR_MASK( + OPA_LINKDOWN_REASON_LOCAL_MEDIA_NOT_INSTALLED)) || + (ppd->offline_disabled_reason == + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_NONE))) + ppd->offline_disabled_reason = HFI2_ODR_MASK( + OPA_LINKDOWN_REASON_LOCAL_MEDIA_NOT_INSTALLED); + + if (ppd->host_link_state == HLS_DN_POLL) { + /* + * The link is still in POLL. This means + * that the normal link down processing + * will not happen. We have to do it here + * before turning the DC off. + */ + queue_work(ppd->link_wq, &ppd->link_down_work); + } + } else { + ppd_dev_info(ppd, "%s: QSFP module inserted\n", + __func__); + + spin_lock_irqsave(&ppd->qsfp_info.qsfp_lock, flags); + ppd->qsfp_info.cache_valid = 0; + ppd->qsfp_info.cache_refresh_required = 1; + spin_unlock_irqrestore(&ppd->qsfp_info.qsfp_lock, + flags); + + /* + * Stop inversion of ModPresent pin to detect + * removal of the cable + */ + qsfp_int_mgmt &= ~(u64)QSFP_HFI0_MODPRST_N; + hfi2_write_csr(dd, + dd->hfi2_id ? ASIC_QSFP2_INVERT : + ASIC_QSFP1_INVERT, + qsfp_int_mgmt); + + ppd->offline_disabled_reason = + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_TRANSIENT); + } + } + + if (reg & QSFP_HFI0_INT_N) { + ppd_dev_info(ppd, "%s: Interrupt received from QSFP module\n", + __func__); + spin_lock_irqsave(&ppd->qsfp_info.qsfp_lock, flags); + ppd->qsfp_info.check_interrupt_flags = 1; + spin_unlock_irqrestore(&ppd->qsfp_info.qsfp_lock, flags); + } + + /* Schedule the QSFP work only if there is a cable attached. */ + if (hfi2_qsfp_mod_present(ppd)) + queue_work(ppd->link_wq, &ppd->qsfp_info.qsfp_work); +} + +static int request_host_lcb_access(struct hfi2_devdata *dd) +{ + int ret; + + ret = do_8051_command(dd, HCMD_MISC, + (u64)HCMD_MISC_REQUEST_LCB_ACCESS + << LOAD_DATA_FIELD_ID_SHIFT, + NULL); + if (ret != HCMD_SUCCESS && !(dd->flags & HFI2_SHUTDOWN)) { + dd_dev_err(dd, "%s: command failed with error %d\n", __func__, + ret); + } + return ret == HCMD_SUCCESS ? 0 : -EBUSY; +} + +static int request_8051_lcb_access(struct hfi2_devdata *dd) +{ + int ret; + + ret = do_8051_command(dd, HCMD_MISC, + (u64)HCMD_MISC_GRANT_LCB_ACCESS + << LOAD_DATA_FIELD_ID_SHIFT, + NULL); + if (ret != HCMD_SUCCESS) { + dd_dev_err(dd, "%s: command failed with error %d\n", __func__, + ret); + } + return ret == HCMD_SUCCESS ? 0 : -EBUSY; +} + +/* + * Set the LCB selector - allow host access. The DCC selector always + * points to the host. + */ +static inline void set_host_lcb_access(struct hfi2_devdata *dd) +{ + hfi2_write_csr(dd, DC_DC8051_CFG_CSR_ACCESS_SEL, + DC_DC8051_CFG_CSR_ACCESS_SEL_DCC_SMASK | + DC_DC8051_CFG_CSR_ACCESS_SEL_LCB_SMASK); +} + +/* + * Clear the LCB selector - allow 8051 access. The DCC selector always + * points to the host. + */ +static inline void set_8051_lcb_access(struct hfi2_devdata *dd) +{ + hfi2_write_csr(dd, DC_DC8051_CFG_CSR_ACCESS_SEL, + DC_DC8051_CFG_CSR_ACCESS_SEL_DCC_SMASK); +} + +/* + * Acquire LCB access from the 8051. If the host already has access, + * just increment a counter. Otherwise, inform the 8051 that the + * host is taking access. + * + * Returns: + * 0 on success + * -EBUSY if the 8051 has control and cannot be disturbed + * -errno if unable to acquire access from the 8051 + */ +int hfi2_acquire_lcb_access(struct hfi2_devdata *dd, int sleep_ok) +{ + struct hfi2_pportdata *ppd = dd->pport; + int ret = 0; + + /* + * Use the host link state lock so the operation of this routine + * { link state check, selector change, count increment } can occur + * as a unit against a link state change. Otherwise there is a + * race between the state change and the count increment. + */ + if (sleep_ok) { + mutex_lock(&ppd->hls_lock); + } else { + while (!mutex_trylock(&ppd->hls_lock)) + udelay(1); + } + + /* this access is valid only when the link is up */ + if (ppd->host_link_state & HLS_DOWN) { + ppd_dev_info(ppd, "%s: link state %s not up\n", __func__, + hfi2_link_state_name(ppd->host_link_state)); + ret = -EBUSY; + goto done; + } + + if (dd->lcb_access_count == 0) { + ret = request_host_lcb_access(dd); + if (ret) { + if (!(dd->flags & HFI2_SHUTDOWN)) + ppd_dev_err( + ppd, + "%s: unable to acquire LCB access, err %d\n", + __func__, ret); + goto done; + } + set_host_lcb_access(dd); + } + dd->lcb_access_count++; +done: + mutex_unlock(&ppd->hls_lock); + return ret; +} + +/* + * Release LCB access by decrementing the use count. If the count is moving + * from 1 to 0, inform 8051 that it has control back. + * + * Returns: + * 0 on success + * -errno if unable to release access to the 8051 + */ +int hfi2_release_lcb_access(struct hfi2_devdata *dd, int sleep_ok) +{ + int ret = 0; + + /* + * Use the host link state lock because the acquire needed it. + * Here, we only need to keep { selector change, count decrement } + * as a unit. + */ + if (sleep_ok) { + mutex_lock(&dd->pport->hls_lock); + } else { + while (!mutex_trylock(&dd->pport->hls_lock)) + udelay(1); + } + + if (dd->lcb_access_count == 0) { + dd_dev_err(dd, "%s: LCB access count is zero. Skipping.\n", + __func__); + goto done; + } + + if (dd->lcb_access_count == 1) { + set_8051_lcb_access(dd); + ret = request_8051_lcb_access(dd); + if (ret) { + dd_dev_err(dd, + "%s: unable to release LCB access, err %d\n", + __func__, ret); + /* restore host access if the grant didn't work */ + set_host_lcb_access(dd); + goto done; + } + } + dd->lcb_access_count--; +done: + mutex_unlock(&dd->pport->hls_lock); + return ret; +} + +/* + * Initialize LCB access variables and state. Called during driver load, + * after most of the initialization is finished. + * + * The DC default is LCB access on for the host. The driver defaults to + * leaving access to the 8051. Assign access now - this constrains the call + * to this routine to be after all LCB set-up is done. In particular, after + * hf1_init_dd() -> set_up_interrupts() -> hfi2_clear_all_interrupts() + */ +static void init_lcb_access(struct hfi2_devdata *dd) +{ + dd->lcb_access_count = 0; +} + +/* + * Write a response back to a 8051 request. + */ +static void hreq_response(struct hfi2_devdata *dd, u8 return_code, u16 rsp_data) +{ + hfi2_write_csr( + dd, DC_DC8051_CFG_EXT_DEV_0, + DC_DC8051_CFG_EXT_DEV_0_COMPLETED_SMASK | + (u64)return_code + << DC_DC8051_CFG_EXT_DEV_0_RETURN_CODE_SHIFT | + (u64)rsp_data + << DC_DC8051_CFG_EXT_DEV_0_RSP_DATA_SHIFT); +} + +/* + * Handle host requests from the 8051. + */ +static void handle_8051_request(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + u16 data = 0; + u8 type; + + reg = hfi2_read_csr(dd, DC_DC8051_CFG_EXT_DEV_1); + if ((reg & DC_DC8051_CFG_EXT_DEV_1_REQ_NEW_SMASK) == 0) + return; /* no request */ + + /* zero out COMPLETED so the response is seen */ + hfi2_write_csr(dd, DC_DC8051_CFG_EXT_DEV_0, 0); + + /* extract request details */ + type = (reg >> DC_DC8051_CFG_EXT_DEV_1_REQ_TYPE_SHIFT) & + DC_DC8051_CFG_EXT_DEV_1_REQ_TYPE_MASK; + data = (reg >> DC_DC8051_CFG_EXT_DEV_1_REQ_DATA_SHIFT) & + DC_DC8051_CFG_EXT_DEV_1_REQ_DATA_MASK; + + switch (type) { + case HREQ_LOAD_CONFIG: + case HREQ_SAVE_CONFIG: + case HREQ_READ_CONFIG: + case HREQ_SET_TX_EQ_ABS: + case HREQ_SET_TX_EQ_REL: + case HREQ_ENABLE: + ppd_dev_info(ppd, "8051 request: request 0x%x not supported\n", + type); + hreq_response(dd, HREQ_NOT_SUPPORTED, 0); + break; + case HREQ_LCB_RESET: + /* Put the LCB, RX FPE and TX FPE into reset */ + hfi2_write_csr(dd, DCC_CFG_RESET, LCB_RX_FPE_TX_FPE_INTO_RESET); + /* Make sure the write completed */ + (void)hfi2_read_csr(dd, DCC_CFG_RESET); + /* Hold the reset long enough to take effect */ + udelay(1); + /* Take the LCB, RX FPE and TX FPE out of reset */ + hfi2_write_csr(dd, DCC_CFG_RESET, + LCB_RX_FPE_TX_FPE_OUT_OF_RESET); + hreq_response(dd, HREQ_SUCCESS, 0); + + break; + case HREQ_CONFIG_DONE: + hreq_response(dd, HREQ_SUCCESS, 0); + break; + + case HREQ_INTERFACE_TEST: + hreq_response(dd, HREQ_SUCCESS, data); + break; + default: + ppd_dev_err(ppd, "8051 request: unknown request 0x%x\n", type); + hreq_response(dd, HREQ_NOT_SUPPORTED, 0); + break; + } +} + +/* + * Set up allocation unit vaulue. + */ +void hfi2_set_up_vau(struct hfi2_pportdata *ppd, u8 vau) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg = read_eport_csr(dd, ppd->hw_pidx, + dd->params->send_cm_global_credit_reg); + + /* do not modify other values in the register */ + reg &= ~SEND_CM_GLOBAL_CREDIT_AU_SMASK; + reg |= (u64)vau << SEND_CM_GLOBAL_CREDIT_AU_SHIFT; + write_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_global_credit_reg, + reg); +} + +/* + * Set up initial VL15 credits of the remote. Assumes the rest of + * the CM credit registers are zero from a previous global or credit reset. + * Shared limit for VL15 will always be 0. + */ +void hfi2_set_up_vl15(struct hfi2_pportdata *ppd, u16 vl15buf) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg = read_eport_csr(dd, ppd->hw_pidx, + dd->params->send_cm_global_credit_reg); + + /* set initial values for total and shared credit limit */ + reg &= ~(SEND_CM_GLOBAL_CREDIT_TOTAL_CREDIT_LIMIT_SMASK | + SEND_CM_GLOBAL_CREDIT_SHARED_LIMIT_SMASK); + + /* + * Set total limit to be equal to VL15 credits. + * Leave shared limit at 0. + */ + reg |= (u64)vl15buf << SEND_CM_GLOBAL_CREDIT_TOTAL_CREDIT_LIMIT_SHIFT; + write_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_global_credit_reg, + reg); + + write_eport_csr( + dd, ppd->hw_pidx, dd->params->send_cm_credit_vl15_reg, + (u64)vl15buf << SEND_CM_CREDIT_VL15_DEDICATED_LIMIT_VL_SHIFT); +} + +/* + * Zero all credit details from the previous connection and + * reset the CM manager's internal counters. + */ +void hfi2_reset_link_credits(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + int i; + + /* remove all previous VL credit limits */ + for (i = 0; i < TXE_NUM_DATA_VL; i++) + write_eport_csr(dd, ppd->hw_pidx, + dd->params->send_cm_credit_vl_reg + (8 * i), 0); + write_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_credit_vl15_reg, + 0); + write_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_global_credit_reg, + 0); + /* reset the CM block */ + hfi2_pio_send_control(ppd, PSC_CM_RESET); + /* reset cached value */ + dd->vl15buf_cached = 0; +} + +/* convert a vCU to a CU */ +static u32 vcu_to_cu(u8 vcu) +{ + return 1 << vcu; +} + +/* convert a CU to a vCU */ +static u8 cu_to_vcu(u32 cu) +{ + return ilog2(cu); +} + +/* convert a vAU to an AU */ +static u32 vau_to_au(u8 vau) +{ + return 8 * (1 << vau); +} + +static void set_linkup_defaults(struct hfi2_pportdata *ppd) +{ + ppd->sm_trap_qp = 0x0; + ppd->sa_qp = 0x1; +} + +/* + * Graceful LCB shutdown. This leaves the LCB FIFOs in reset. + */ +static void lcb_shutdown(struct hfi2_devdata *dd, int abort) +{ + u64 reg; + + /* clear lcb run: LCB_CFG_RUN.EN = 0 */ + hfi2_write_csr(dd, DC_LCB_CFG_RUN, 0); + /* set tx fifo reset: LCB_CFG_TX_FIFOS_RESET.VAL = 1 */ + hfi2_write_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET, + 1ull << DC_LCB_CFG_TX_FIFOS_RESET_VAL_SHIFT); + /* set dcc reset csr: DCC_CFG_RESET.{reset_lcb,reset_rx_fpe} = 1 */ + dd->lcb_err_en = hfi2_read_csr(dd, DC_LCB_ERR_EN); + reg = hfi2_read_csr(dd, DCC_CFG_RESET); + hfi2_write_csr(dd, DCC_CFG_RESET, + reg | DCC_CFG_RESET_RESET_LCB | + DCC_CFG_RESET_RESET_RX_FPE); + (void)hfi2_read_csr(dd, + DCC_CFG_RESET); /* make sure the write completed */ + if (!abort) { + udelay(1); /* must hold for the longer of 16cclks or 20ns */ + hfi2_write_csr(dd, DCC_CFG_RESET, reg); + hfi2_write_csr(dd, DC_LCB_ERR_EN, dd->lcb_err_en); + } +} + +/* + * This routine should be called after the link has been transitioned to + * OFFLINE (OFFLINE state has the side effect of putting the SerDes into + * reset). + * + * The expectation is that the caller of this routine would have taken + * care of properly transitioning the link into the correct state. + * NOTE: the caller needs to acquire the dd->dc8051_lock lock + * before calling this function. + */ +static void _dc_shutdown(struct hfi2_devdata *dd) +{ + lockdep_assert_held(&dd->dc8051_lock); + + if (dd->dc_shutdown) + return; + + dd->dc_shutdown = 1; + /* Shutdown the LCB */ + lcb_shutdown(dd, 1); + /* + * Going to OFFLINE would have causes the 8051 to put the + * SerDes into reset already. Just need to shut down the 8051, + * itself. + */ + hfi2_write_csr(dd, DC_DC8051_CFG_RST, 0x1); +} + +static void dc_shutdown(struct hfi2_devdata *dd) +{ + mutex_lock(&dd->dc8051_lock); + _dc_shutdown(dd); + mutex_unlock(&dd->dc8051_lock); +} + +/* + * Calling this after the DC has been brought out of reset should not + * do any damage. + * NOTE: the caller needs to acquire the dd->dc8051_lock lock + * before calling this function. + */ +static void _dc_start(struct hfi2_devdata *dd) +{ + lockdep_assert_held(&dd->dc8051_lock); + + if (!dd->dc_shutdown) + return; + + /* Take the 8051 out of reset */ + hfi2_write_csr(dd, DC_DC8051_CFG_RST, 0ull); + /* Wait until 8051 is ready */ + if (hfi2_wait_fm_ready(dd, TIMEOUT_8051_START)) + dd_dev_err(dd, "%s: timeout starting 8051 firmware\n", + __func__); + + /* Take away reset for LCB and RX FPE (set in lcb_shutdown). */ + hfi2_write_csr(dd, DCC_CFG_RESET, LCB_RX_FPE_TX_FPE_OUT_OF_RESET); + /* lcb_shutdown() with abort=1 does not restore these */ + hfi2_write_csr(dd, DC_LCB_ERR_EN, dd->lcb_err_en); + dd->dc_shutdown = 0; +} + +static void dc_start(struct hfi2_devdata *dd) +{ + mutex_lock(&dd->dc8051_lock); + _dc_start(dd); + mutex_unlock(&dd->dc8051_lock); +} + +/* + * Handle a SMA idle message + * + * This is a work-queue function outside of the interrupt. + */ +void hfi2_handle_sma_message(struct work_struct *work) +{ + struct hfi2_pportdata *ppd = + container_of(work, struct hfi2_pportdata, sma_message_work); + struct hfi2_devdata *dd = ppd->dd; + u64 msg; + int ret; + + /* + * msg is bytes 1-4 of the 40-bit idle message - the command code + * is stripped off + */ + ret = read_idle_sma(dd, &msg); + if (ret) + return; + ppd_dev_info(ppd, "%s: SMA message 0x%llx\n", __func__, msg); + /* + * React to the SMA message. Byte[1] (0 for us) is the command. + */ + switch (msg & 0xff) { + case SMA_IDLE_ARM: + /* + * See OPAv1 table 9-14 - HFI and External Switch Ports Key + * State Transitions + * + * Only expected in INIT or ARMED, discard otherwise. + */ + if (ppd->host_link_state & (HLS_UP_INIT | HLS_UP_ARMED)) + ppd->neighbor_normal = 1; + break; + case SMA_IDLE_ACTIVE: + /* + * See OPAv1 table 9-14 - HFI and External Switch Ports Key + * State Transitions + * + * Can activate the node. Discard otherwise. + */ + if (ppd->host_link_state == HLS_UP_ARMED && + ppd->is_active_optimize_enabled) { + ppd->neighbor_normal = 1; + ret = hfi2_set_link_state(ppd, HLS_UP_ACTIVE); + if (ret) + ppd_dev_err( + ppd, + "%s: received Active SMA idle message, couldn't set link to Active\n", + __func__); + } + break; + default: + ppd_dev_err(ppd, + "%s: received unexpected SMA idle message 0x%llx\n", + __func__, msg); + break; + } +} + +/* + * Clear first, then set. This allows for multi-bit fields to be set. + * + * Performs same action on hfi2_loopback RcvIportCtrl register if SRIOV. + */ +static void adjust_rcvctrl(struct hfi2_pportdata *ppd, u64 add, u64 clear) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 rcvctrl; + unsigned long flags; + + spin_lock_irqsave(&dd->rcvctrl_lock, flags); + rcvctrl = read_iport_csr(dd, ppd->hw_pidx, + dd->params->rcv_iport_ctrl_reg); + rcvctrl &= ~clear; + rcvctrl |= add; + write_iport_csr(dd, ppd->hw_pidx, dd->params->rcv_iport_ctrl_reg, + rcvctrl); + if (dd->is_sriov) { + rcvctrl = read_iport_csr(dd, loopback_pidx(ppd), + dd->params->rcv_iport_ctrl_reg); + rcvctrl &= ~clear; + rcvctrl |= add; + write_iport_csr(dd, loopback_pidx(ppd), + dd->params->rcv_iport_ctrl_reg, rcvctrl); + } + spin_unlock_irqrestore(&dd->rcvctrl_lock, flags); +} + +static inline void add_rcvctrl(struct hfi2_pportdata *ppd, u64 add) +{ + adjust_rcvctrl(ppd, add, 0); +} + +static inline void clear_rcvctrl(struct hfi2_pportdata *ppd, u64 clear) +{ + adjust_rcvctrl(ppd, 0, clear); +} + +static void set_wcb(struct hfi2_pportdata *ppd, int wcb) +{ + u64 set; + + /* reject anything out of range, fields are the same for all chips */ + if ((wcb & JKR_RCV_IPORT_CTRL_RCV_WCB_MASK) != wcb) + return; + set = (u64)wcb << JKR_RCV_IPORT_CTRL_RCV_WCB_SHIFT; + adjust_rcvctrl(ppd, set, JKR_RCV_IPORT_CTRL_RCV_WCB_SMASK); +} + +/* + * Steps needed to handle active PIO and SDMA when a link goes down. Not + * called at interrupt time. + * + * This is an alternative to an SPC freeze for link down. It depends on the + * hardware ability to flush packets when in the wrong link state. WFR does + * not have this feature and should continue to perform an SPC freeze on link + * down. + */ +void hfi2_start_linkdown_handling(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + struct send_context *sc; + int sc_flags; + int i; + + /* + * Stop step + * + * SDMA: Keep the engines running - other ports may be using them. + * Expect all descriptors bound for the down port to be processed, + * but the contents dropped. This ability is only available on + * hardware after WFR. Pre-WFR, the descriptor would stall. + * + * PIO: Halt, with linkdown flag, all of the enabled send contexts for + * this port. + */ + sc_flags = SCF_LINK_DOWN; + for (i = 0; i < dd->num_send_contexts; i++) { + sc = dd->send_contexts[i].sc; + if (!sc || sc->ppd != ppd || !(sc->flags & SCF_ENABLED)) + continue; + + hfi2_sc_stop(sc, sc_flags); + } + + /* + * Disable step + * + * SDMA: The engines are left running. Nothing to do. + * + * PIO: Disable all contexts for this port. Non-user contexts will be + * re-enabled at linkup time. User contexts will be re-enabled when + * the user requests a context reset. + */ + for (i = 0; i < dd->num_send_contexts; i++) { + sc = dd->send_contexts[i].sc; + if (!sc || sc->ppd != ppd) + continue; + + hfi2_sc_disable(sc); + } +} + +/* + * Called from all interrupt handlers to start handling an SPC freeze. + */ +void hfi2_start_freeze_handling(struct hfi2_devdata *dd, int flags) +{ + struct send_context *sc; + int i; + int sc_flags; + + if (flags & FREEZE_SELF) + hfi2_write_csr(dd, CCE_CTRL, CCE_CTRL_SPC_FREEZE_SMASK); + + /* enter frozen mode */ + dd->flags |= HFI2_FROZEN; + + /* notify all SDMA engines that they are going into a freeze */ + hfi2_sdma_freeze_notify(dd, !!(flags & FREEZE_LINK_DOWN)); + + sc_flags = SCF_FROZEN | SCF_HALTED | + (flags & FREEZE_LINK_DOWN ? SCF_LINK_DOWN : 0); + /* do halt pre-handling on all enabled send contexts */ + for (i = 0; i < dd->num_send_contexts; i++) { + sc = dd->send_contexts[i].sc; + if (sc && (sc->flags & SCF_ENABLED)) + hfi2_sc_stop(sc, sc_flags); + } + + /* Send context are frozen. Notify user space */ + for (i = 0; i < dd->num_pports; i++) + hfi2_set_uevent_bits(&dd->pport[i], _HFI2_EVENT_FROZEN_BIT); + + if (flags & FREEZE_ABORT) { + dd_dev_err(dd, + "Aborted freeze recovery. Please REBOOT system\n"); + return; + } + /* queue non-interrupt handler */ + queue_work(dd->hfi2_wq, &dd->freeze_work); +} + +/* + * Wait until all 4 sub-blocks indicate that they have frozen or unfrozen, + * depending on the "freeze" parameter. + * + * No need to return an error if it times out, our only option + * is to proceed anyway. + */ +static void wait_for_freeze_status(struct hfi2_devdata *dd, int freeze) +{ + unsigned long timeout; + u64 reg; + + timeout = jiffies + msecs_to_jiffies(FREEZE_STATUS_TIMEOUT); + while (1) { + reg = hfi2_read_csr(dd, CCE_STATUS); + if (freeze) { + /* waiting until all indicators are set */ + if ((reg & ALL_FROZE) == ALL_FROZE) + return; /* all done */ + } else { + /* waiting until all indicators are clear */ + if ((reg & ALL_FROZE) == 0) + return; /* all done */ + } + + if (time_after(jiffies, timeout)) { + dd_dev_err( + dd, + "Time out waiting for SPC %sfreeze, bits 0x%llx, expecting 0x%llx, continuing", + freeze ? "" : "un", reg & ALL_FROZE, + freeze ? ALL_FROZE : 0ull); + return; + } + usleep_range(80, 120); + } +} + +/* + * Do all freeze handling for the RXE block. + */ +static void rxe_freeze(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + int i, j; + struct hfi2_ctxtdata *rcd; + + /* disable all receive contexts */ + for (i = 0; i < dd->num_pports; i++) { + struct hfi2_portrsrcs *pr = &dr->ppr[i]; + + /* disable port */ + clear_rcvctrl(&dd->pport[i], RCV_CTRL_RCV_PORT_ENABLE_SMASK); + + for (j = 0; j < pr->num_rcv_contexts; j++) { + u16 ctxt = pr->rcv_context_base + j; + + rcd = hfi2_rcd_get_by_index(dd, ctxt); + hfi2_rcvctrl(dd, HFI2_RCVCTRL_CTXT_DIS, rcd); + hfi2_rcd_put(rcd); + } + } +} + +/* + * Unfreeze handling for the RXE block - kernel contexts only. + * This will also enable the port. User contexts will do unfreeze + * handling on a per-context basis as they call into the driver. + */ +static void rxe_kernel_unfreeze(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + struct hfi2_ctxtdata *rcd; + u32 rcvmask; + u16 i; + u16 j; + + /* enable all kernel contexts */ + for (i = 0; i < dd->num_pports; i++) { + struct hfi2_pportdata *ppd = dd->pport + i; + struct hfi2_portrsrcs *pr = &dr->ppr[i]; + + for (j = 0; j < pr->num_rcv_contexts; j++) { + u16 ctxt = pr->rcv_context_base + j; + + rcd = hfi2_rcd_get_by_index(dd, ctxt); + /* + * Ensure all non-user contexts are enabled. + */ + if (!rcd || hfi2_is_user_context(rcd)) { + hfi2_rcd_put(rcd); + continue; + } + rcvmask = HFI2_RCVCTRL_CTXT_ENB; + /* HFI2_RCVCTRL_TAILUPD_* needs to be set explicitly */ + rcvmask |= hfi2_rcvhdrtail_kvaddr(rcd) ? + HFI2_RCVCTRL_TAILUPD_ENB : + HFI2_RCVCTRL_TAILUPD_DIS; + hfi2_rcvctrl(dd, rcvmask, rcd); + hfi2_rcd_put(rcd); + } + + /* enable port */ + add_rcvctrl(ppd, RCV_CTRL_RCV_PORT_ENABLE_SMASK); + } +} + +/* + * Non-interrupt SPC freeze handling. + * + * This is a work-queue function outside of the triggering interrupt. + */ +void hfi2_handle_freeze(struct work_struct *work) +{ + struct hfi2_devdata *dd = + container_of(work, struct hfi2_devdata, freeze_work); + + /* wait for freeze indicators on all affected blocks */ + wait_for_freeze_status(dd, 1); + + /* SPC is now frozen */ + + /* do send PIO freeze steps */ + hfi2_pio_freeze(dd); + + /* do send DMA freeze steps */ + hfi2_sdma_freeze(dd); + + /* do send egress freeze steps - nothing to do */ + + /* do receive freeze steps */ + rxe_freeze(dd); + + /* + * Unfreeze the hardware - clear the freeze, wait for each + * block's frozen bit to clear, then clear the frozen flag. + */ + hfi2_write_csr(dd, CCE_CTRL, CCE_CTRL_SPC_UNFREEZE_SMASK); + wait_for_freeze_status(dd, 0); + + if (hfi2_is_ax(dd)) { + hfi2_write_csr(dd, CCE_CTRL, CCE_CTRL_SPC_FREEZE_SMASK); + wait_for_freeze_status(dd, 1); + hfi2_write_csr(dd, CCE_CTRL, CCE_CTRL_SPC_UNFREEZE_SMASK); + wait_for_freeze_status(dd, 0); + } + + /* do send PIO unfreeze steps for kernel contexts */ + hfi2_pio_kernel_unfreeze(dd); + + /* do send DMA unfreeze steps */ + hfi2_sdma_unfreeze(dd); + + /* do send egress unfreeze steps - nothing to do */ + + /* do receive unfreeze steps for kernel contexts */ + rxe_kernel_unfreeze(dd); + + /* + * The unfreeze procedure touches global device registers when + * it disables and re-enables RXE. Mark the device unfrozen + * after all that is done so other parts of the driver waiting + * for the device to unfreeze don't do things out of order. + * + * The above implies that the meaning of HFI2_FROZEN flag is + * "Device has gone into freeze mode and freeze mode handling + * is still in progress." + * + * The flag will be removed when freeze mode processing has + * completed. + */ + dd->flags &= ~HFI2_FROZEN; + wake_up(&dd->event_queue); + + /* no longer frozen */ +} + +/** + * hfi2_update_xmit_counters - update PortXmitWait/PortVlXmitWait + * counters. + * @ppd: info of physical Hfi port + * @link_width: new link width after link up or downgrade + * + * Update the PortXmitWait and PortVlXmitWait counters after + * a link up or downgrade event to reflect a link width change. + */ +void hfi2_update_xmit_counters(struct hfi2_pportdata *ppd, u16 link_width) +{ + int i; + u16 tx_width; + u16 link_speed; + + tx_width = hfi2_tx_link_width(link_width); + link_speed = get_link_speed(ppd->link_speed_active); + + /* + * There are C_VL_COUNT number of PortVLXmitWait counters. + * Adding 1 to C_VL_COUNT to include the PortXmitWait counter. + */ + for (i = 0; i < C_VL_COUNT + 1; i++) + hfi2_get_xmit_wait_counters(ppd, tx_width, link_speed, i); +} + +static void read_ltp_rtt(struct hfi2_pportdata *ppd) +{ + u64 reg; + + if (hfi2_read_lcb_csr(ppd, DC_LCB_STS_ROUND_TRIP_LTP_CNT, ®)) { + ppd_dev_err(ppd, "%s: unable to read LTP RTT\n", __func__); + ppd->link_ltp_rtt = 0; + } else { + ppd->link_ltp_rtt = reg; + } +} + +/* + * Handle a link up interrupt from the 8051. + * + * This is a work-queue function outside of the interrupt. + */ +void hfi2_handle_link_up(struct work_struct *work) +{ + struct hfi2_pportdata *ppd = + container_of(work, struct hfi2_pportdata, link_up_work); + struct hfi2_devdata *dd = ppd->dd; + + hfi2_set_link_state(ppd, HLS_UP_INIT); + + /* cache the read of DC_LCB_STS_ROUND_TRIP_LTP_CNT */ + read_ltp_rtt(ppd); + /* + * OPA specifies that certain counters are cleared on a transition + * to link up, so do that. + */ + hfi2_clear_linkup_counters(ppd); + /* + * And (re)set link up default values. + */ + set_linkup_defaults(ppd); + + /* + * Set VL15 credits. Use cached value from verify cap interrupt. + * In case of quick linkup, vl15 value will be set by + * hfi2_handle_linkup_change. VerifyCap interrupt handler will not be + * called in those scenarios. + */ + if (!hfi2_quick_linkup) + hfi2_set_up_vl15(ppd, dd->vl15buf_cached); + + /* enforce link speed enabled */ + if ((ppd->link_speed_active & ppd->link_speed_enabled) == 0) { + /* oops - current speed is not enabled, bounce */ + ppd_dev_err( + ppd, + "Link speed active 0x%x is outside enabled 0x%x, downing link\n", + ppd->link_speed_active, ppd->link_speed_enabled); + hfi2_set_link_down_reason(ppd, OPA_LINKDOWN_REASON_SPEED_POLICY, + 0, OPA_LINKDOWN_REASON_SPEED_POLICY); + hfi2_set_link_state(ppd, HLS_DN_OFFLINE); + hfi2_start_link(ppd); + } +} + +/* + * Several pieces of LNI information were cached for SMA in ppd. + * Reset these on link down + */ +static void reset_neighbor_info(struct hfi2_pportdata *ppd) +{ + ppd->neighbor_guid = 0; + ppd->neighbor_port_number = 0; + ppd->neighbor_type = 0; + ppd->neighbor_fm_security = 0; +} + +static const char *const link_down_reason_strs[] = { + [OPA_LINKDOWN_REASON_NONE] = "None", + [OPA_LINKDOWN_REASON_RCV_ERROR_0] = "Receive error 0", + [OPA_LINKDOWN_REASON_BAD_PKT_LEN] = "Bad packet length", + [OPA_LINKDOWN_REASON_PKT_TOO_LONG] = "Packet too long", + [OPA_LINKDOWN_REASON_PKT_TOO_SHORT] = "Packet too short", + [OPA_LINKDOWN_REASON_BAD_SLID] = "Bad SLID", + [OPA_LINKDOWN_REASON_BAD_DLID] = "Bad DLID", + [OPA_LINKDOWN_REASON_BAD_L2] = "Bad L2", + [OPA_LINKDOWN_REASON_BAD_SC] = "Bad SC", + [OPA_LINKDOWN_REASON_RCV_ERROR_8] = "Receive error 8", + [OPA_LINKDOWN_REASON_BAD_MID_TAIL] = "Bad mid tail", + [OPA_LINKDOWN_REASON_RCV_ERROR_10] = "Receive error 10", + [OPA_LINKDOWN_REASON_PREEMPT_ERROR] = "Preempt error", + [OPA_LINKDOWN_REASON_PREEMPT_VL15] = "Preempt vl15", + [OPA_LINKDOWN_REASON_BAD_VL_MARKER] = "Bad VL marker", + [OPA_LINKDOWN_REASON_RCV_ERROR_14] = "Receive error 14", + [OPA_LINKDOWN_REASON_RCV_ERROR_15] = "Receive error 15", + [OPA_LINKDOWN_REASON_BAD_HEAD_DIST] = "Bad head distance", + [OPA_LINKDOWN_REASON_BAD_TAIL_DIST] = "Bad tail distance", + [OPA_LINKDOWN_REASON_BAD_CTRL_DIST] = "Bad control distance", + [OPA_LINKDOWN_REASON_BAD_CREDIT_ACK] = "Bad credit ack", + [OPA_LINKDOWN_REASON_UNSUPPORTED_VL_MARKER] = "Unsupported VL marker", + [OPA_LINKDOWN_REASON_BAD_PREEMPT] = "Bad preempt", + [OPA_LINKDOWN_REASON_BAD_CONTROL_FLIT] = "Bad control flit", + [OPA_LINKDOWN_REASON_EXCEED_MULTICAST_LIMIT] = "Exceed multicast limit", + [OPA_LINKDOWN_REASON_RCV_ERROR_24] = "Receive error 24", + [OPA_LINKDOWN_REASON_RCV_ERROR_25] = "Receive error 25", + [OPA_LINKDOWN_REASON_RCV_ERROR_26] = "Receive error 26", + [OPA_LINKDOWN_REASON_RCV_ERROR_27] = "Receive error 27", + [OPA_LINKDOWN_REASON_RCV_ERROR_28] = "Receive error 28", + [OPA_LINKDOWN_REASON_RCV_ERROR_29] = "Receive error 29", + [OPA_LINKDOWN_REASON_RCV_ERROR_30] = "Receive error 30", + [OPA_LINKDOWN_REASON_EXCESSIVE_BUFFER_OVERRUN] = + "Excessive buffer overrun", + [OPA_LINKDOWN_REASON_UNKNOWN] = "Unknown", + [OPA_LINKDOWN_REASON_REBOOT] = "Reboot", + [OPA_LINKDOWN_REASON_NEIGHBOR_UNKNOWN] = "Neighbor unknown", + [OPA_LINKDOWN_REASON_FM_BOUNCE] = "FM bounce", + [OPA_LINKDOWN_REASON_SPEED_POLICY] = "Speed policy", + [OPA_LINKDOWN_REASON_WIDTH_POLICY] = "Width policy", + [OPA_LINKDOWN_REASON_DISCONNECTED] = "Disconnected", + [OPA_LINKDOWN_REASON_LOCAL_MEDIA_NOT_INSTALLED] = + "Local media not installed", + [OPA_LINKDOWN_REASON_NOT_INSTALLED] = "Not installed", + [OPA_LINKDOWN_REASON_CHASSIS_CONFIG] = "Chassis config", + [OPA_LINKDOWN_REASON_END_TO_END_NOT_INSTALLED] = + "End to end not installed", + [OPA_LINKDOWN_REASON_POWER_POLICY] = "Power policy", + [OPA_LINKDOWN_REASON_LINKSPEED_POLICY] = "Link speed policy", + [OPA_LINKDOWN_REASON_LINKWIDTH_POLICY] = "Link width policy", + [OPA_LINKDOWN_REASON_SWITCH_MGMT] = "Switch management", + [OPA_LINKDOWN_REASON_SMA_DISABLED] = "SMA disabled", + [OPA_LINKDOWN_REASON_TRANSIENT] = "Transient" +}; + +/* return the neighbor link down reason string */ +static const char *link_down_reason_str(u8 reason) +{ + const char *str = NULL; + + if (reason < ARRAY_SIZE(link_down_reason_strs)) + str = link_down_reason_strs[reason]; + if (!str) + str = "(invalid)"; + + return str; +} + +/* + * Handle a link down interrupt from the 8051. + * + * This is a work-queue function outside of the interrupt. + */ +void hfi2_handle_link_down(struct work_struct *work) +{ + u8 lcl_reason, neigh_reason = 0; + u8 link_down_reason; + struct hfi2_pportdata *ppd = + container_of(work, struct hfi2_pportdata, link_down_work); + int was_up; + static const char ldr_str[] = "Link down reason: "; + + if ((ppd->host_link_state & + (HLS_DN_POLL | HLS_VERIFY_CAP | HLS_GOING_UP)) && + ppd->port_type == PORT_TYPE_FIXED) + ppd->offline_disabled_reason = + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_NOT_INSTALLED); + + /* Go offline first, then deal with reading/writing through 8051 */ + was_up = !!(ppd->host_link_state & HLS_UP); + hfi2_set_link_state(ppd, HLS_DN_OFFLINE); + xchg(&ppd->is_link_down_queued, 0); + + if (was_up) { + lcl_reason = 0; + /* link down reason is only valid if the link was up */ + read_link_down_reason(ppd->dd, &link_down_reason); + switch (link_down_reason) { + case LDR_LINK_TRANSFER_ACTIVE_LOW: + /* the link went down, no idle message reason */ + ppd_dev_info(ppd, "%sUnexpected link down\n", ldr_str); + break; + case LDR_RECEIVED_LINKDOWN_IDLE_MSG: + /* + * The neighbor reason is only valid if an idle message + * was received for it. + */ + read_planned_down_reason_code(ppd->dd, &neigh_reason); + ppd_dev_info(ppd, + "%sNeighbor link down message %d, %s\n", + ldr_str, neigh_reason, + link_down_reason_str(neigh_reason)); + break; + case LDR_RECEIVED_HOST_OFFLINE_REQ: + ppd_dev_info(ppd, + "%sHost requested link to go offline\n", + ldr_str); + break; + default: + ppd_dev_info(ppd, "%sUnknown reason 0x%x\n", ldr_str, + link_down_reason); + break; + } + + /* + * If no reason, assume peer-initiated but missed + * LinkGoingDown idle flits. + */ + if (neigh_reason == 0) + lcl_reason = OPA_LINKDOWN_REASON_NEIGHBOR_UNKNOWN; + } else { + /* went down while polling or going up */ + lcl_reason = OPA_LINKDOWN_REASON_TRANSIENT; + } + + hfi2_set_link_down_reason(ppd, lcl_reason, neigh_reason, 0); + + /* inform the SMA when the link transitions from up to down */ + if (was_up && ppd->local_link_down_reason.sma == 0 && + ppd->neigh_link_down_reason.sma == 0) { + ppd->local_link_down_reason.sma = + ppd->local_link_down_reason.latest; + ppd->neigh_link_down_reason.sma = + ppd->neigh_link_down_reason.latest; + } + + reset_neighbor_info(ppd); + + /* disable the port */ + clear_rcvctrl(ppd, RCV_CTRL_RCV_PORT_ENABLE_SMASK); + + /* + * If there is no cable attached, turn the DC off. Otherwise, + * start the link bring up. + */ + if (ppd->port_type == PORT_TYPE_QSFP && !hfi2_qsfp_mod_present(ppd)) + dc_shutdown(ppd->dd); + else + hfi2_start_link(ppd); +} + +void hfi2_wfr_handle_link_bounce(struct work_struct *work) +{ + struct hfi2_pportdata *ppd = + container_of(work, struct hfi2_pportdata, link_bounce_work); + + /* + * Only do something if the link is currently up. + */ + if (ppd->host_link_state & HLS_UP) { + hfi2_set_link_state(ppd, HLS_DN_OFFLINE); + hfi2_start_link(ppd); + } else { + ppd_dev_info(ppd, "%s: link not up (%s), nothing to do\n", + __func__, + hfi2_link_state_name(ppd->host_link_state)); + } +} + +/* + * Mask conversion: Capability exchange to Port LTP. The capability + * exchange has an implicit 16b CRC that is mandatory. + */ +static int cap_to_port_ltp(int cap) +{ + int port_ltp = PORT_LTP_CRC_MODE_16; /* this mode is mandatory */ + + if (cap & CAP_CRC_14B) + port_ltp |= PORT_LTP_CRC_MODE_14; + if (cap & CAP_CRC_48B) + port_ltp |= PORT_LTP_CRC_MODE_48; + if (cap & CAP_CRC_12B_16B_PER_LANE) + port_ltp |= PORT_LTP_CRC_MODE_PER_LANE; + + return port_ltp; +} + +/* + * Convert an OPA Port LTP mask to capability mask + */ +int hfi2_port_ltp_to_cap(int port_ltp) +{ + int cap_mask = 0; + + if (port_ltp & PORT_LTP_CRC_MODE_14) + cap_mask |= CAP_CRC_14B; + if (port_ltp & PORT_LTP_CRC_MODE_48) + cap_mask |= CAP_CRC_48B; + if (port_ltp & PORT_LTP_CRC_MODE_PER_LANE) + cap_mask |= CAP_CRC_12B_16B_PER_LANE; + + return cap_mask; +} + +/* + * Convert a single DC LCB CRC mode to an OPA Port LTP mask. + */ +static int lcb_to_port_ltp(int lcb_crc) +{ + int port_ltp = 0; + + if (lcb_crc == LCB_CRC_12B_16B_PER_LANE) + port_ltp = PORT_LTP_CRC_MODE_PER_LANE; + else if (lcb_crc == LCB_CRC_48B) + port_ltp = PORT_LTP_CRC_MODE_48; + else if (lcb_crc == LCB_CRC_14B) + port_ltp = PORT_LTP_CRC_MODE_14; + else + port_ltp = PORT_LTP_CRC_MODE_16; + + return port_ltp; +} + +static void clear_full_mgmt_pkey(struct hfi2_pportdata *ppd) +{ + if (ppd->pkeys[2] != 0) { + ppd->pkeys[2] = 0; + (void)hfi2_set_ib_cfg(ppd, HFI2_IB_CFG_PKEYS, 0); + hfi2_event_pkey_change(ppd->dd, ppd->port); + } +} + +/* + * Convert the given link width to the OPA link width bitmask. + */ +static u16 link_width_to_bits(struct hfi2_devdata *dd, u16 width) +{ + switch (width) { + case 0: + /* + * Quick linkup does not set the width. + * Just set it to 4x without complaint. + */ + if (hfi2_quick_linkup) + return OPA_LINK_WIDTH_4X; + return 0; /* no lanes up */ + case 1: + return OPA_LINK_WIDTH_1X; + case 2: + return OPA_LINK_WIDTH_2X; + case 3: + return OPA_LINK_WIDTH_3X; + case 4: + return OPA_LINK_WIDTH_4X; + default: + dd_dev_info(dd, "%s: invalid width %d, using 4\n", __func__, + width); + return OPA_LINK_WIDTH_4X; + } +} + +/* + * Do a population count on the bottom nibble. + */ +static const u8 bit_counts[16] = { 0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4 }; + +static inline u8 nibble_to_count(u8 nibble) +{ + return bit_counts[nibble & 0xf]; +} + +/* + * Read the active lane information from the 8051 registers and return + * their widths. + * + * Active lane information is found in these 8051 registers: + * enable_lane_tx + * enable_lane_rx + */ +static void get_link_widths(struct hfi2_pportdata *ppd, u16 *tx_width, + u16 *rx_width) +{ + struct hfi2_devdata *dd = ppd->dd; + u16 tx, rx; + u8 enable_lane_rx; + u8 enable_lane_tx; + u8 tx_polarity_inversion; + u8 rx_polarity_inversion; + u8 max_rate; + + /* read the active lanes */ + read_tx_settings(dd, &enable_lane_tx, &tx_polarity_inversion, + &rx_polarity_inversion, &max_rate); + read_local_lni(dd, &enable_lane_rx); + + /* convert to counts */ + tx = nibble_to_count(enable_lane_tx); + rx = nibble_to_count(enable_lane_rx); + + /* + * Set link_speed_active here, overriding what was set in + * hfi2_handle_verify_cap(). The ASIC 8051 firmware does not correctly + * set the max_rate field in hfi2_handle_verify_cap until v0.19. + */ + if ((dd->icode == ICODE_RTL_SILICON) && + (dd->dc8051_ver < dc8051_ver(0, 19, 0))) { + /* max_rate: 0 = 12.5G, 1 = 25G */ + switch (max_rate) { + case 0: + ppd->link_speed_active = OPA_LINK_SPEED_12_5G; + break; + case 1: + ppd->link_speed_active = OPA_LINK_SPEED_25G; + break; + default: + ppd_dev_err(ppd, + "%s: unexpected max rate %d, using 25Gb\n", + __func__, (int)max_rate); + ppd->link_speed_active = OPA_LINK_SPEED_25G; + break; + } + } + + ppd_dev_info( + ppd, + "Fabric active lanes (width): tx 0x%x (%d), rx 0x%x (%d)\n", + enable_lane_tx, tx, enable_lane_rx, rx); + *tx_width = link_width_to_bits(dd, tx); + *rx_width = link_width_to_bits(dd, rx); +} + +/* + * Read verify_cap_local_fm_link_width[1] to obtain the link widths. + * Valid after the end of VerifyCap and during LinkUp. Does not change + * after link up. I.e. look elsewhere for downgrade information. + * + * Bits are: + * + bits [7:4] contain the number of active transmitters + * + bits [3:0] contain the number of active receivers + * These are numbers 1 through 4 and can be different values if the + * link is asymmetric. + * + * verify_cap_local_fm_link_width[0] retains its original value. + */ +static void get_linkup_widths(struct hfi2_pportdata *ppd, u16 *tx_width, + u16 *rx_width) +{ + struct hfi2_devdata *dd = ppd->dd; + u16 widths, tx, rx; + u8 misc_bits, local_flags; + u16 active_tx, active_rx; + + read_vc_local_link_mode(dd, &misc_bits, &local_flags, &widths); + tx = widths >> 12; + rx = (widths >> 8) & 0xf; + + *tx_width = link_width_to_bits(dd, tx); + *rx_width = link_width_to_bits(dd, rx); + + /* print the active widths */ + get_link_widths(ppd, &active_tx, &active_rx); +} + +/* + * Set ppd->link_width_active and ppd->link_width_downgrade_active using + * hardware information when the link first comes up. + * + * The link width is not available until after VerifyCap.AllFramesReceived + * (the trigger for hfi2_handle_verify_cap), so this is outside that routine + * and should be called when the 8051 signals linkup. + */ +void hfi2_get_linkup_link_widths(struct hfi2_pportdata *ppd) +{ + /* + * WFR queries the DC for link widths after LNI. JKR does not use + * the DC for this; hardcode the known link parameters instead. + */ + if (ppd->dd->params->chip_type == CHIP_WFR) { + u16 tx_width, rx_width; + + /* get end-of-LNI link widths */ + get_linkup_widths(ppd, &tx_width, &rx_width); + + /* use tx_width as the link is supposed to be symmetric on link up */ + ppd->link_width_active = tx_width; + } else { + /* + * JKR operates at 25G/4X. Set speed and width explicitly + * since JKR does not negotiate these via the DC. + */ + ppd->link_speed_active = OPA_LINK_SPEED_25G; + ppd->link_speed_enabled = OPA_LINK_SPEED_25G; + ppd->link_width_active = OPA_LINK_WIDTH_4X; + } + /* link width downgrade active (LWD.A) starts out matching LW.A */ + ppd->link_width_downgrade_tx_active = ppd->link_width_active; + ppd->link_width_downgrade_rx_active = ppd->link_width_active; + /* per OPA spec, on link up LWD.E resets to LWD.S */ + ppd->link_width_downgrade_enabled = ppd->link_width_downgrade_supported; + /* cache the active egress rate (units {10^6 bits/sec]) */ + ppd->current_egress_rate = active_egress_rate(ppd); +} + +/* + * Handle a verify capabilities interrupt from the 8051. + * + * This is a work-queue function outside of the interrupt. + */ +void hfi2_handle_verify_cap(struct work_struct *work) +{ + struct hfi2_pportdata *ppd = + container_of(work, struct hfi2_pportdata, link_vc_work); + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + u8 power_management; + u8 continuous; + u8 vcu; + u8 vau; + u8 z; + u16 vl15buf; + u16 link_widths; + u16 crc_mask; + u16 crc_val; + u16 device_id; + u16 active_tx, active_rx; + u8 partner_supported_crc; + u8 remote_tx_rate; + u8 device_rev; + + hfi2_set_link_state(ppd, HLS_VERIFY_CAP); + + lcb_shutdown(dd, 0); + + read_vc_remote_phy(dd, &power_management, &continuous); + read_vc_remote_fabric(dd, &vau, &z, &vcu, &vl15buf, + &partner_supported_crc); + read_vc_remote_link_width(dd, &remote_tx_rate, &link_widths); + read_remote_device_id(dd, &device_id, &device_rev); + + /* print the active widths */ + get_link_widths(ppd, &active_tx, &active_rx); + ppd_dev_info( + ppd, + "Peer PHY: power management 0x%x, continuous updates 0x%x\n", + (int)power_management, (int)continuous); + ppd_dev_info( + ppd, + "Peer Fabric: vAU %d, Z %d, vCU %d, vl15 credits 0x%x, CRC sizes 0x%x\n", + (int)vau, (int)z, (int)vcu, (int)vl15buf, + (int)partner_supported_crc); + ppd_dev_info(ppd, "Peer Link Width: tx rate 0x%x, widths 0x%x\n", + (u32)remote_tx_rate, (u32)link_widths); + ppd_dev_info(ppd, "Peer Device ID: 0x%04x, Revision 0x%02x\n", + (u32)device_id, (u32)device_rev); + /* + * The peer vAU value just read is the peer receiver value. HFI does + * not support a transmit vAU of 0 (AU == 8). We advertised that + * with Z=1 in the fabric capabilities sent to the peer. The peer + * will see our Z=1, and, if it advertised a vAU of 0, will move its + * receive to vAU of 1 (AU == 16). Do the same here. We do not care + * about the peer Z value - our sent vAU is 3 (hardwired) and is not + * subject to the Z value exception. + */ + if (vau == 0) + vau = 1; + hfi2_set_up_vau(ppd, vau); + + /* + * Set VL15 credits to 0 in global credit register. Cache remote VL15 + * credits value and wait for link-up interrupt ot set it. + */ + hfi2_set_up_vl15(ppd, 0); + dd->vl15buf_cached = vl15buf; + + /* set up the LCB CRC mode */ + crc_mask = ppd->port_crc_mode_enabled & partner_supported_crc; + + /* order is important: use the lowest bit in common */ + if (crc_mask & CAP_CRC_14B) + crc_val = LCB_CRC_14B; + else if (crc_mask & CAP_CRC_48B) + crc_val = LCB_CRC_48B; + else if (crc_mask & CAP_CRC_12B_16B_PER_LANE) + crc_val = LCB_CRC_12B_16B_PER_LANE; + else + crc_val = LCB_CRC_16B; + + ppd_dev_info(ppd, "Final LCB CRC mode: %d\n", (int)crc_val); + hfi2_write_csr(dd, DC_LCB_CFG_CRC_MODE, + (u64)crc_val << DC_LCB_CFG_CRC_MODE_TX_VAL_SHIFT); + + /* set (14b only) or clear sideband credit */ + reg = read_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_ctrl_reg); + if (crc_val == LCB_CRC_14B && crc_14b_sideband) { + write_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_ctrl_reg, + reg | SEND_CM_CTRL_FORCE_CREDIT_MODE_SMASK); + } else { + write_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_ctrl_reg, + reg & ~SEND_CM_CTRL_FORCE_CREDIT_MODE_SMASK); + } + + ppd->link_speed_active = 0; /* invalid value */ + if (dd->dc8051_ver < dc8051_ver(0, 20, 0)) { + /* remote_tx_rate: 0 = 12.5G, 1 = 25G */ + switch (remote_tx_rate) { + case 0: + ppd->link_speed_active = OPA_LINK_SPEED_12_5G; + break; + case 1: + ppd->link_speed_active = OPA_LINK_SPEED_25G; + break; + } + } else { + /* actual rate is highest bit of the ANDed rates */ + u8 rate = remote_tx_rate & ppd->local_tx_rate; + + if (rate & 2) + ppd->link_speed_active = OPA_LINK_SPEED_25G; + else if (rate & 1) + ppd->link_speed_active = OPA_LINK_SPEED_12_5G; + } + if (ppd->link_speed_active == 0) { + ppd_dev_err(ppd, + "%s: unexpected remote tx rate %d, using 25Gb\n", + __func__, (int)remote_tx_rate); + ppd->link_speed_active = OPA_LINK_SPEED_25G; + } + + /* + * Cache the values of the supported, enabled, and active + * LTP CRC modes to return in 'portinfo' queries. But the bit + * flags that are returned in the portinfo query differ from + * what's in the hfi2_link_crc_mask, crc_sizes, and crc_val + * variables. Convert these here. + */ + ppd->port_ltp_crc_mode = cap_to_port_ltp(hfi2_link_crc_mask) << 8; + /* supported crc modes */ + ppd->port_ltp_crc_mode |= cap_to_port_ltp(ppd->port_crc_mode_enabled) + << 4; + /* enabled crc modes */ + ppd->port_ltp_crc_mode |= lcb_to_port_ltp(crc_val); + /* active crc mode */ + + /* set up the remote credit return table */ + hfi2_assign_remote_cm_au_table(ppd, vcu); + + /* + * The LCB is reset on entry to hfi2_handle_verify_cap(), so this must + * be applied on every link up. + * + * Adjust LCB error kill enable to kill the link if + * these RBUF errors are seen: + * REPLAY_BUF_MBE_SMASK + * FLIT_INPUT_BUF_MBE_SMASK + */ + if (hfi2_is_ax(dd)) { /* fixed in B0 */ + reg = hfi2_read_csr(dd, DC_LCB_CFG_LINK_KILL_EN); + reg |= DC_LCB_CFG_LINK_KILL_EN_REPLAY_BUF_MBE_SMASK | + DC_LCB_CFG_LINK_KILL_EN_FLIT_INPUT_BUF_MBE_SMASK; + hfi2_write_csr(dd, DC_LCB_CFG_LINK_KILL_EN, reg); + } + + /* pull LCB fifos out of reset - all fifo clocks must be stable */ + hfi2_write_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET, 0); + + /* give 8051 access to the LCB CSRs */ + hfi2_write_csr(dd, DC_LCB_ERR_EN, 0); /* mask LCB errors */ + set_8051_lcb_access(dd); + + /* tell the 8051 to go to LinkUp */ + hfi2_set_link_state(ppd, HLS_GOING_UP); +} + +/** + * hfi2_apply_link_downgrade_policy - Apply the link width downgrade enabled + * policy against the current active link widths. + * @ppd: info of physical Hfi port + * @refresh_widths: True indicates link downgrade event + * @return: True indicates a successful link downgrade. False indicates + * link downgrade event failed and the link will bounce back to + * default link width. + * + * Called when the enabled policy changes or the active link widths + * change. + * Refresh_widths indicates that a link downgrade occurred. The + * link_downgraded variable is set by refresh_widths and + * determines the success/failure of the policy application. + */ +bool hfi2_apply_link_downgrade_policy(struct hfi2_pportdata *ppd, + bool refresh_widths) +{ + int do_bounce = 0; + int tries; + u16 lwde; + u16 tx, rx; + bool link_downgraded = refresh_widths; + + /* use the hls lock to avoid a race with actual link up */ + tries = 0; +retry: + mutex_lock(&ppd->hls_lock); + /* only apply if the link is up */ + if (ppd->host_link_state & HLS_DOWN) { + /* still going up..wait and retry */ + if (ppd->host_link_state & HLS_GOING_UP) { + if (++tries < 1000) { + mutex_unlock(&ppd->hls_lock); + usleep_range(100, 120); /* arbitrary */ + goto retry; + } + ppd_dev_err( + ppd, + "%s: giving up waiting for link state change\n", + __func__); + } + goto done; + } + + lwde = ppd->link_width_downgrade_enabled; + + if (refresh_widths) { + get_link_widths(ppd, &tx, &rx); + ppd->link_width_downgrade_tx_active = tx; + ppd->link_width_downgrade_rx_active = rx; + } + + if (ppd->link_width_downgrade_tx_active == 0 || + ppd->link_width_downgrade_rx_active == 0) { + /* the 8051 reported a dead link as a downgrade */ + ppd_dev_err(ppd, + "Link downgrade is really a link down, ignoring\n"); + link_downgraded = false; + } else if (lwde == 0) { + /* downgrade is disabled */ + + /* bounce if not at starting active width */ + if ((ppd->link_width_active != + ppd->link_width_downgrade_tx_active) || + (ppd->link_width_active != + ppd->link_width_downgrade_rx_active)) { + ppd_dev_err( + ppd, + "Link downgrade is disabled and link has downgraded, downing link\n"); + ppd_dev_err( + ppd, + " original 0x%x, tx active 0x%x, rx active 0x%x\n", + ppd->link_width_active, + ppd->link_width_downgrade_tx_active, + ppd->link_width_downgrade_rx_active); + do_bounce = 1; + link_downgraded = false; + } + } else if ((lwde & ppd->link_width_downgrade_tx_active) == 0 || + (lwde & ppd->link_width_downgrade_rx_active) == 0) { + /* Tx or Rx is outside the enabled policy */ + ppd_dev_err( + ppd, + "Link is outside of downgrade allowed, downing link\n"); + ppd_dev_err(ppd, + " enabled 0x%x, tx active 0x%x, rx active 0x%x\n", + lwde, ppd->link_width_downgrade_tx_active, + ppd->link_width_downgrade_rx_active); + do_bounce = 1; + link_downgraded = false; + } + +done: + mutex_unlock(&ppd->hls_lock); + + if (do_bounce) { + hfi2_set_link_down_reason(ppd, OPA_LINKDOWN_REASON_WIDTH_POLICY, + 0, OPA_LINKDOWN_REASON_WIDTH_POLICY); + hfi2_set_link_state(ppd, HLS_DN_OFFLINE); + hfi2_start_link(ppd); + } + + return link_downgraded; +} + +/* + * Handle a link downgrade interrupt from the 8051. + * + * This is a work-queue function outside of the interrupt. + */ +void hfi2_handle_link_downgrade(struct work_struct *work) +{ + struct hfi2_pportdata *ppd = + container_of(work, struct hfi2_pportdata, link_downgrade_work); + + ppd_dev_info(ppd, "8051: Link width downgrade\n"); + if (hfi2_apply_link_downgrade_policy(ppd, true)) + hfi2_update_xmit_counters(ppd, + ppd->link_width_downgrade_tx_active); +} + +static char *dcc_err_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, dcc_err_flags, + ARRAY_SIZE(dcc_err_flags)); +} + +static char *lcb_err_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, lcb_err_flags, + ARRAY_SIZE(lcb_err_flags)); +} + +static char *dc8051_err_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, dc8051_err_flags, + ARRAY_SIZE(dc8051_err_flags)); +} + +static char *dc8051_info_err_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, dc8051_info_err_flags, + ARRAY_SIZE(dc8051_info_err_flags)); +} + +static char *dc8051_info_host_msg_string(char *buf, int buf_len, u64 flags) +{ + return flag_string(buf, buf_len, flags, dc8051_info_host_msg_flags, + ARRAY_SIZE(dc8051_info_host_msg_flags)); +} + +static void handle_8051_interrupt(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + struct hfi2_pportdata *ppd = dd->pport; + u64 info, err, host_msg; + int queue_link_down = 0; + char buf[96]; + + /* look at the flags */ + if (reg & DC_DC8051_ERR_FLG_SET_BY_8051_SMASK) { + /* 8051 information set by firmware */ + /* read DC8051_DBG_ERR_INFO_SET_BY_8051 for details */ + info = hfi2_read_csr(dd, DC_DC8051_DBG_ERR_INFO_SET_BY_8051); + err = (info >> DC_DC8051_DBG_ERR_INFO_SET_BY_8051_ERROR_SHIFT) & + DC_DC8051_DBG_ERR_INFO_SET_BY_8051_ERROR_MASK; + host_msg = (info >> + DC_DC8051_DBG_ERR_INFO_SET_BY_8051_HOST_MSG_SHIFT) & + DC_DC8051_DBG_ERR_INFO_SET_BY_8051_HOST_MSG_MASK; + + /* + * Handle error flags. + */ + if (err & FAILED_LNI) { + /* + * LNI error indications are cleared by the 8051 + * only when starting polling. Only pay attention + * to them when in the states that occur during + * LNI. + */ + if (ppd->host_link_state & + (HLS_DN_POLL | HLS_VERIFY_CAP | HLS_GOING_UP)) { + queue_link_down = 1; + ppd_dev_info(ppd, "Link error: %s\n", + dc8051_info_err_string( + buf, sizeof(buf), + err & FAILED_LNI)); + } + err &= ~(u64)FAILED_LNI; + } + /* unknown frames can happen durning LNI, just count */ + if (err & UNKNOWN_FRAME) { + ppd->unknown_frame_count++; + err &= ~(u64)UNKNOWN_FRAME; + } + if (err) { + /* report remaining errors, but do not do anything */ + ppd_dev_err(ppd, "8051 info error: %s\n", + dc8051_info_err_string(buf, sizeof(buf), + err)); + } + + /* + * Handle host message flags. + */ + if (host_msg & HOST_REQ_DONE) { + /* + * Presently, the driver does a busy wait for + * host requests to complete. This is only an + * informational message. + * NOTE: The 8051 clears the host message + * information *on the next 8051 command*. + * Therefore, when linkup is achieved, + * this flag will still be set. + */ + host_msg &= ~(u64)HOST_REQ_DONE; + } + if (host_msg & BC_SMA_MSG) { + queue_work(ppd->link_wq, &ppd->sma_message_work); + host_msg &= ~(u64)BC_SMA_MSG; + } + if (host_msg & LINKUP_ACHIEVED) { + ppd_dev_info(ppd, "8051: Link up\n"); + queue_work(ppd->link_wq, &ppd->link_up_work); + host_msg &= ~(u64)LINKUP_ACHIEVED; + } + if (host_msg & EXT_DEVICE_CFG_REQ) { + handle_8051_request(ppd); + host_msg &= ~(u64)EXT_DEVICE_CFG_REQ; + } + if (host_msg & VERIFY_CAP_FRAME) { + queue_work(ppd->link_wq, &ppd->link_vc_work); + host_msg &= ~(u64)VERIFY_CAP_FRAME; + } + if (host_msg & LINK_GOING_DOWN) { + const char *extra = ""; + /* no downgrade action needed if going down */ + if (host_msg & LINK_WIDTH_DOWNGRADED) { + host_msg &= ~(u64)LINK_WIDTH_DOWNGRADED; + extra = " (ignoring downgrade)"; + } + dd_dev_info(dd, "8051: Link down%s\n", extra); + queue_link_down = 1; + host_msg &= ~(u64)LINK_GOING_DOWN; + } + if (host_msg & LINK_WIDTH_DOWNGRADED) { + queue_work(ppd->link_wq, &ppd->link_downgrade_work); + host_msg &= ~(u64)LINK_WIDTH_DOWNGRADED; + } + if (host_msg) { + /* report remaining messages, but do not do anything */ + ppd_dev_info(ppd, "8051 info host message: %s\n", + dc8051_info_host_msg_string( + buf, sizeof(buf), host_msg)); + } + + reg &= ~DC_DC8051_ERR_FLG_SET_BY_8051_SMASK; + } + if (reg & DC_DC8051_ERR_FLG_LOST_8051_HEART_BEAT_SMASK) { + /* + * Lost the 8051 heartbeat. If this happens, we + * receive constant interrupts about it. Disable + * the interrupt after the first. + */ + ppd_dev_err(ppd, "Lost 8051 heartbeat\n"); + hfi2_write_csr( + dd, DC_DC8051_ERR_EN, + hfi2_read_csr(dd, DC_DC8051_ERR_EN) & + ~DC_DC8051_ERR_EN_LOST_8051_HEART_BEAT_SMASK); + + reg &= ~DC_DC8051_ERR_FLG_LOST_8051_HEART_BEAT_SMASK; + } + if (reg) { + /* report the error, but do not do anything */ + ppd_dev_err(ppd, "8051 error: %s\n", + dc8051_err_string(buf, sizeof(buf), reg)); + } + + if (queue_link_down) { + /* + * if the link is already going down or disabled, do not + * queue another. If there's a link down entry already + * queued, don't queue another one. + */ + if ((ppd->host_link_state & + (HLS_GOING_OFFLINE | HLS_LINK_COOLDOWN)) || + ppd->link_enabled == 0) { + ppd_dev_info( + ppd, + "%s: not queuing link down. host_link_state %x, link_enabled %x\n", + __func__, ppd->host_link_state, + ppd->link_enabled); + } else { + if (xchg(&ppd->is_link_down_queued, 1) == 1) + ppd_dev_info( + ppd, + "%s: link down request already queued\n", + __func__); + else + queue_work(ppd->link_wq, &ppd->link_down_work); + } + } +} + +static const char *const fm_config_txt[] = { + [0] = "BadHeadDist: Distance violation between two head flits", + [1] = "BadTailDist: Distance violation between two tail flits", + [2] = "BadCtrlDist: Distance violation between two credit control flits", + [3] = "BadCrdAck: Credits return for unsupported VL", + [4] = "UnsupportedVLMarker: Received VL Marker", + [5] = "BadPreempt: Exceeded the preemption nesting level", + [6] = "BadControlFlit: Received unsupported control flit", + /* no 7 */ + [8] = "UnsupportedVLMarker: Received VL Marker for unconfigured or disabled VL", +}; + +static const char *const port_rcv_txt[] = { + [1] = "BadPktLen: Illegal PktLen", + [2] = "PktLenTooLong: Packet longer than PktLen", + [3] = "PktLenTooShort: Packet shorter than PktLen", + [4] = "BadSLID: Illegal SLID (0, using multicast as SLID, does not include security validation of SLID)", + [5] = "BadDLID: Illegal DLID (0, doesn't match HFI)", + [6] = "BadL2: Illegal L2 opcode", + [7] = "BadSC: Unsupported SC", + [9] = "BadRC: Illegal RC", + [11] = "PreemptError: Preempting with same VL", + [12] = "PreemptVL15: Preempting a VL15 packet", +}; + +#define OPA_LDR_FMCONFIG_OFFSET 16 +#define OPA_LDR_PORTRCV_OFFSET 0 +static void handle_dcc_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + u64 info, hdr0, hdr1; + const char *extra; + char buf[96]; + struct hfi2_pportdata *ppd = dd->pport; + u8 lcl_reason = 0; + int do_bounce = 0; + + if (reg & DCC_ERR_FLG_UNCORRECTABLE_ERR_SMASK) { + if (!(dd->err_info_uncorrectable & OPA_EI_STATUS_SMASK)) { + info = hfi2_read_csr(dd, DCC_ERR_INFO_UNCORRECTABLE); + dd->err_info_uncorrectable = info & OPA_EI_CODE_SMASK; + /* set status bit */ + dd->err_info_uncorrectable |= OPA_EI_STATUS_SMASK; + } + reg &= ~DCC_ERR_FLG_UNCORRECTABLE_ERR_SMASK; + } + + if (reg & DCC_ERR_FLG_LINK_ERR_SMASK) { + /* this counter saturates at (2^32) - 1 */ + if (ppd->link_downed < (u32)UINT_MAX) + ppd->link_downed++; + reg &= ~DCC_ERR_FLG_LINK_ERR_SMASK; + } + + if (reg & DCC_ERR_FLG_FMCONFIG_ERR_SMASK) { + u8 reason_valid = 1; + + info = hfi2_read_csr(dd, DCC_ERR_INFO_FMCONFIG); + if (!(dd->err_info_fmconfig & OPA_EI_STATUS_SMASK)) { + dd->err_info_fmconfig = info & OPA_EI_CODE_SMASK; + /* set status bit */ + dd->err_info_fmconfig |= OPA_EI_STATUS_SMASK; + } + switch (info) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + extra = fm_config_txt[info]; + break; + case 8: + extra = fm_config_txt[info]; + if (ppd->port_error_action & + OPA_PI_MASK_FM_CFG_UNSUPPORTED_VL_MARKER) { + do_bounce = 1; + /* + * lcl_reason cannot be derived from info + * for this error + */ + lcl_reason = + OPA_LINKDOWN_REASON_UNSUPPORTED_VL_MARKER; + } + break; + default: + reason_valid = 0; + snprintf(buf, sizeof(buf), "reserved%lld", info); + extra = buf; + break; + } + + if (reason_valid && !do_bounce) { + do_bounce = ppd->port_error_action & + (1 << (OPA_LDR_FMCONFIG_OFFSET + info)); + lcl_reason = info + OPA_LINKDOWN_REASON_BAD_HEAD_DIST; + } + + /* just report this */ + dd_dev_info_ratelimited(dd, "DCC Error: fmconfig error: %s\n", + extra); + reg &= ~DCC_ERR_FLG_FMCONFIG_ERR_SMASK; + } + + if (reg & DCC_ERR_FLG_RCVPORT_ERR_SMASK) { + u8 reason_valid = 1; + + info = hfi2_read_csr(dd, DCC_ERR_INFO_PORTRCV); + hdr0 = hfi2_read_csr(dd, DCC_ERR_INFO_PORTRCV_HDR0); + hdr1 = hfi2_read_csr(dd, DCC_ERR_INFO_PORTRCV_HDR1); + if (!(dd->err_info_rcvport.status_and_code & + OPA_EI_STATUS_SMASK)) { + dd->err_info_rcvport.status_and_code = + info & OPA_EI_CODE_SMASK; + /* set status bit */ + dd->err_info_rcvport.status_and_code |= + OPA_EI_STATUS_SMASK; + /* + * save first 2 flits in the packet that caused + * the error + */ + dd->err_info_rcvport.packet_flit1 = hdr0; + dd->err_info_rcvport.packet_flit2 = hdr1; + } + switch (info) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 9: + case 11: + case 12: + extra = port_rcv_txt[info]; + break; + default: + reason_valid = 0; + snprintf(buf, sizeof(buf), "reserved%lld", info); + extra = buf; + break; + } + + if (reason_valid && !do_bounce) { + do_bounce = ppd->port_error_action & + (1 << (OPA_LDR_PORTRCV_OFFSET + info)); + lcl_reason = info + OPA_LINKDOWN_REASON_RCV_ERROR_0; + } + + /* just report this */ + dd_dev_info_ratelimited( + dd, + "DCC Error: PortRcv error: %s\n" + " hdr0 0x%llx, hdr1 0x%llx\n", + extra, hdr0, hdr1); + + reg &= ~DCC_ERR_FLG_RCVPORT_ERR_SMASK; + } + + if (reg & DCC_ERR_FLG_EN_CSR_ACCESS_BLOCKED_UC_SMASK) { + /* informative only */ + dd_dev_info_ratelimited(dd, "8051 access to LCB blocked\n"); + reg &= ~DCC_ERR_FLG_EN_CSR_ACCESS_BLOCKED_UC_SMASK; + } + if (reg & DCC_ERR_FLG_EN_CSR_ACCESS_BLOCKED_HOST_SMASK) { + /* informative only */ + dd_dev_info_ratelimited(dd, "host access to LCB blocked\n"); + reg &= ~DCC_ERR_FLG_EN_CSR_ACCESS_BLOCKED_HOST_SMASK; + } + + if (unlikely(hfi2_dbg_fault_suppress_err(&dd->verbs_dev))) + reg &= ~DCC_ERR_FLG_LATE_EBP_ERR_SMASK; + + /* report any remaining errors */ + if (reg) + dd_dev_info_ratelimited(dd, "DCC Error: %s\n", + dcc_err_string(buf, sizeof(buf), reg)); + + if (lcl_reason == 0) + lcl_reason = OPA_LINKDOWN_REASON_UNKNOWN; + + if (do_bounce) { + dd_dev_info_ratelimited(dd, "%s: PortErrorAction bounce\n", + __func__); + hfi2_set_link_down_reason(ppd, lcl_reason, 0, lcl_reason); + queue_work(ppd->link_wq, &ppd->link_bounce_work); + } +} + +static void handle_lcb_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + char buf[96]; + + dd_dev_info(dd, "LCB Error: %s\n", + lcb_err_string(buf, sizeof(buf), reg)); +} + +/* + * CCE block DC interrupt. Source is < 8. + */ +static void is_dc_int(struct hfi2_devdata *dd, unsigned int source) +{ + const struct err_reg_info *eri = &dc_errs[source]; + + if (eri->handler) { + hfi2_interrupt_clear_down(dd, 0, eri); + } else if (source == 3 /* dc_lbm_int */) { + /* + * This indicates that a parity error has occurred on the + * address/control lines presented to the LBM. The error + * is a single pulse, there is no associated error flag, + * and it is non-maskable. This is because if a parity + * error occurs on the request the request is dropped. + * This should never occur, but it is nice to know if it + * ever does. + */ + dd_dev_err(dd, "Parity error in DC LBM block\n"); + } else { + dd_dev_err(dd, "Invalid DC interrupt %u\n", source); + } +} + +/* + * TX block send credit interrupt. + */ +void hfi2_is_send_credit_int(struct hfi2_devdata *dd, unsigned int source) +{ + hfi2_sc_group_release_update(dd, source); +} + +/* + * TX block SDMA interrupt. Source is < 48. + * + * SDMA interrupts are grouped by type: + * + * 0 - N-1 = SDma + * N - 2N-1 = SDmaProgress + * 2N - 3N-1 = SDmaIdle + */ +void hfi2_is_sdma_eng_int(struct hfi2_devdata *dd, unsigned int source) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + /* what interrupt */ + unsigned int what = source / TXE_NUM_SDMA_ENGINES; + /* which engine */ + unsigned int which = source % TXE_NUM_SDMA_ENGINES; + +#ifdef CONFIG_HFI2_SDMA_VERBOSITY + dd_dev_err(dd, "CONFIG SDMA(%u) %s:%d %s()\n", which, + slashstrip(__FILE__), __LINE__, __func__); +#endif + + if (likely(what < 3 && which >= dr->first_sdma_engine && + which < dr->last_sdma_engine)) { +#ifdef CONFIG_HFI2_SDMA_VERBOSITY + sdma_dumpstate(&dd->per_sdma[which]); +#endif + hfi2_sdma_engine_interrupt(&dd->per_sdma[which], + 1ull << source); + } else { + /* should not happen */ + dd_dev_err(dd, "Invalid SDMA interrupt 0x%x\n", source); + } +} + +/** + * hfi2_is_rcv_avail_int() - User receive context available IRQ handler + * @dd: valid dd + * @source: logical IRQ source (offset from IS_RCVAVAIL_START) + * + * RX block receive available interrupt. + * + * This is the general interrupt handler for user (PSM) receive contexts, + * and can only be used for non-threaded IRQs. + */ +void hfi2_is_rcv_avail_int(struct hfi2_devdata *dd, unsigned int source) +{ + struct hfi2_ctxtdata *rcd; + + rcd = hfi2_rcd_get_by_index(dd, source); + if (rcd) { + hfi2_handle_user_interrupt(rcd); + hfi2_rcd_put(rcd); + return; /* OK */ + } + dd_dev_err( + dd, + "unexpected dataless receive available context interrupt %u\n", + source); +} + +/** + * hfi2_is_rcv_urgent_int() - User receive context urgent IRQ handler + * @dd: valid dd + * @source: logical IRQ source (offset from IS_RCVURGENT_START) + * + * RX block receive urgent interrupt. + * + * NOTE: kernel receive contexts specifically do NOT enable this IRQ. + */ +void hfi2_is_rcv_urgent_int(struct hfi2_devdata *dd, unsigned int source) +{ + struct hfi2_ctxtdata *rcd; + + rcd = hfi2_rcd_get_by_index(dd, source); + if (rcd) { + hfi2_handle_user_interrupt(rcd); + hfi2_rcd_put(rcd); + return; /* OK */ + } + dd_dev_err(dd, + "unexpected dataless receive urgent context interrupt %u\n", + source); +} + +/* + * Reserved range interrupt. Should not be called in normal operation. + */ +static void is_reserved_int(struct hfi2_devdata *dd, unsigned int source) +{ + char name[64]; + + dd_dev_err(dd, "unexpected %s interrupt\n", + is_reserved_name(name, sizeof(name), source)); +} + +const struct hfi2_is_table hfi2_is_table[] = { + /* + * start end + * name func interrupt func + */ + { IS_GENERAL_ERR_START, IS_GENERAL_ERR_END, is_misc_err_name, + is_misc_err_int }, + { IS_SDMAENG_ERR_START, IS_SDMAENG_ERR_END, hfi2_is_sdma_eng_err_name, + is_sdma_eng_err_int }, + { IS_SENDCTXT_ERR_START, IS_SENDCTXT_ERR_END, hfi2_is_sendctxt_err_name, + hfi2_is_sendctxt_err_int }, + { IS_SDMA_START, IS_SDMA_IDLE_END, hfi2_is_sdma_eng_name, + hfi2_is_sdma_eng_int }, + { IS_VARIOUS_START, IS_VARIOUS_END, is_various_name, is_various_int }, + { IS_DC_START, IS_DC_END, is_dc_name, is_dc_int }, + { IS_RCVAVAIL_START, IS_RCVAVAIL_END, hfi2_is_rcv_avail_name, + hfi2_is_rcv_avail_int }, + { IS_RCVURGENT_START, IS_RCVURGENT_END, hfi2_is_rcv_urgent_name, + hfi2_is_rcv_urgent_int }, + { IS_SENDCREDIT_START, IS_SENDCREDIT_END, hfi2_is_send_credit_name, + hfi2_is_send_credit_int }, + { IS_RESERVED_START, IS_RESERVED_END, is_reserved_name, + is_reserved_int }, + { 0, 0, 0, 0 } /* terminator */ +}; + +/* + * General interrupt sources to enable. This is all sources but SDMA + * (SdmaEngErr, Sdma, SdmaProgress, SdmaIdle), and Receive (RcvAvail, + * RcvUrgent). + */ +const struct gi_enable_entry hfi2_wfr_gi_enable_table[] = { + { IS_GENERAL_ERR_START, IS_GENERAL_ERR_END }, + { IS_SENDCTXT_ERR_START, IS_SENDCTXT_ERR_END }, + { IS_VARIOUS_START, IS_VARIOUS_END }, + { IS_DC_START, IS_DC_END }, + { IS_SENDCREDIT_START, IS_SENDCREDIT_END }, + { 1, 0 } /* terminator */ +}; + +/* + * Interrupt source interrupt - called when the given source has an interrupt. + * Source is a bit index into an array of 64-bit integers. + */ +static void is_interrupt(struct hfi2_devdata *dd, unsigned int source) +{ + const struct hfi2_is_table *entry; + + /* avoids a double compare by walking the table in-order */ + for (entry = &dd->params->hfi2_is_table[0]; entry->is_name; entry++) { + if (source <= entry->end) { + trace_hfi2_interrupt(dd, entry, source); + entry->is_int(dd, source - entry->start); + return; + } + } + /* fell off the end */ + dd_dev_err(dd, "invalid interrupt source %u\n", source); +} + +/** + * hfi2_general_interrupt - General interrupt handler + * @irq: MSIx IRQ vector + * @data: hfi2 devdata + * + * This is able to correctly handle all non-threaded interrupts. Receive + * context DATA IRQs are threaded and are not supported by this handler. + * + */ +irqreturn_t hfi2_general_interrupt(int irq, void *data) +{ + struct hfi2_devdata *dd = data; + u64 regs[LARGEST_NUM_INT_CSRS]; + u64 mask; + u32 bit; + u32 num_int_csrs = dd->params->num_int_csrs; + u32 cce_int_clear_reg = dd->params->cce_int_clear_reg; + u32 cce_int_status_reg = dd->params->cce_int_status_reg; + int i; + irqreturn_t handled = IRQ_NONE; + + this_cpu_inc(*dd->int_counter); + + /* phase 1: scan and clear all handled interrupts */ + for (i = 0; i < num_int_csrs; i++) { + /* create mask from hw masked and remapped */ + mask = dd->gi_mask[i].cce_int_mask & dd->gi_mask[i].remap; + if (mask == 0) { + regs[i] = 0; /* used later */ + continue; + } + regs[i] = hfi2_read_csr(dd, cce_int_status_reg + (8 * i)) & + mask; + /* only clear if anything is set */ + if (regs[i]) + hfi2_write_csr(dd, cce_int_clear_reg + (8 * i), + regs[i]); + } + + /* phase 2: call the appropriate handler */ + for_each_set_bit(bit, (unsigned long *)®s[0], num_int_csrs * 64) { + is_interrupt(dd, bit); + handled = IRQ_HANDLED; + } + + return handled; +} + +static inline void __hfi2_sde_eoi_intr(struct sdma_engine *sde, u32 off, + u64 status) +{ + struct hfi2_devdata *dd = sde->dd; + + /* clear the interrupt(s) *after* handling them */ + hfi2_write_csr(dd, dd->params->cce_int_clear_reg + off, status); + if (hfi2_sdma_work_pending(sde)) + hfi2_write_csr(dd, dd->params->cce_int_force_reg + off, + sde->int_mask); +} + +irqreturn_t hfi2_sdma_interrupt(int irq, void *data) +{ + struct sdma_engine *sde = data; + struct hfi2_devdata *dd = sde->dd; + u64 status; + u32 off; + +#ifdef CONFIG_HFI2_SDMA_VERBOSITY + dd_dev_err(dd, "CONFIG SDMA(%u) %s:%d %s()\n", sde->this_idx, + slashstrip(__FILE__), __LINE__, __func__); + sdma_dumpstate(sde); +#endif + + this_cpu_inc(*dd->int_counter); + + /* This hfi2_read_csr is really bad in the hot path */ + off = 8 * (dd->params->is_sdma_start / 64); + status = hfi2_read_csr(dd, dd->params->cce_int_status_reg + off) & + sde->imask; + if (likely(status)) { + /* handle the interrupt(s) */ + hfi2_sdma_engine_interrupt(sde, status); + if (hfi2_sdma_work_pending(sde)) + return IRQ_WAKE_THREAD; + __hfi2_sde_eoi_intr(sde, off, status); + } else { + dd_dev_info_ratelimited( + dd, + "SDMA engine %u interrupt, but no status bits set\n", + sde->this_idx); + } + return IRQ_HANDLED; +} + +irqreturn_t hfi2_sdma_interrupt_thr(int irq, void *data) +{ + struct sdma_engine *sde = data; + struct hfi2_devdata *dd = sde->dd; + u64 status; + u32 off; + unsigned long ty; + unsigned long flags; + + /* This hfi2_read_csr is really bad in the hot path */ + off = 8 * (dd->params->is_sdma_start / 64); + status = hfi2_read_csr(dd, dd->params->cce_int_status_reg + off) & + sde->imask; + ty = jiffies + msecs_to_jiffies(sdma_yield); + if (likely(status)) { +again: + /* handle the interrupt(s) */ + hfi2_sdma_engine_interrupt(sde, status); + + if (hfi2_sdma_work_pending(sde)) { + if (time_after(jiffies, ty)) { + cond_resched(); + ty = jiffies + msecs_to_jiffies(sdma_yield); + } + goto again; + } + local_irq_save(flags); + __hfi2_sde_eoi_intr(sde, off, status); + local_irq_restore(flags); + } else { + dd_dev_info_ratelimited( + dd, + "SDMA engine %u interrupt, but no status bits set\n", + sde->this_idx); + } + return IRQ_HANDLED; +} + +/* + * Force a 0->1 transition on the given interrupt number. + * + * Caller is responsible for any complications that might + * arise from other concurrent activity. + */ +void hfi2_force_intr(struct hfi2_devdata *dd, u16 nr) +{ + u32 reg = (nr / 64) * 8; + u64 bit = 1ull << (nr % 64); + + /* clear bit first, to be sure it is off */ + hfi2_write_csr(dd, dd->params->cce_int_clear_reg + reg, bit); + /* force the above write on the chip */ + hfi2_read_csr(dd, dd->params->cce_int_clear_reg + reg); + + hfi2_write_csr(dd, dd->params->cce_int_force_reg + reg, bit); +} + +/* + * Clear the receive interrupt. Use a read of the interrupt clear CSR + * to insure that the write completed. This does NOT guarantee that + * queued DMA writes to memory from the chip are pushed. + */ +static inline void clear_recv_intr(struct hfi2_ctxtdata *rcd) +{ + struct hfi2_devdata *dd = rcd->dd; + u32 addr = dd->params->cce_int_clear_reg + (8 * rcd->ireg); + + hfi2_write_csr(dd, addr, rcd->imask); + /* force the above write on the chip and get a value back */ + (void)hfi2_read_csr(dd, addr); +} + +/* force the receive interrupt */ +void hfi2_force_recv_intr(struct hfi2_ctxtdata *rcd) +{ + hfi2_write_csr(rcd->dd, + rcd->dd->params->cce_int_force_reg + (8 * rcd->ireg), + rcd->imask); +} + +/* + * Return the receive sequence from the given RHF. This routine is intended + * to be called without a previously determined chip type, so it needs to + * check the chip type to correctly extract the RHF field. Hence "slow". + */ +u32 hfi2_slow_rhf_rcv_seq(struct hfi2_ctxtdata *rcd, u64 rhf) +{ + if (rcd->dd->params->chip_type == CHIP_WFR) + return wfr_rhf_rcv_seq(rhf); + return jkr_rhf_rcv_seq(rhf); +} + +static bool hfi2_packet_present(struct hfi2_ctxtdata *rcd) +{ + if (likely(!rcd->rcvhdrtail_kvaddr)) { + u32 seq = hfi2_slow_rhf_rcv_seq(rcd, + rhf_to_cpu(get_rhf_addr(rcd))); + + return !last_rcv_seq(rcd, seq); + } + return hfi2_rcd_head(rcd) != get_rcvhdrtail(rcd); +} + +/* + * Return non-zero if a packet is present. + * + * This routine is called when rechecking for packets after the RcvAvail + * interrupt has been cleared down. First, do a quick check of memory for + * a packet present. If not found, use an expensive CSR read of the context + * tail to determine the actual tail. The CSR read is necessary because there + * is no method to push pending DMAs to memory other than an interrupt and we + * are trying to determine if we need to force an interrupt. + */ +static inline int check_packet_present(struct hfi2_ctxtdata *rcd) +{ + u32 tail; + + if (hfi2_packet_present(rcd)) + return 1; + + /* fall back to a CSR read, correct indpendent of DMA_RTAIL */ + tail = (u32)read_uctxt_csr(rcd->dd, rcd->ctxt, + rcd->dd->params->rcv_hdr_tail_reg); + return hfi2_rcd_head(rcd) != tail; +} + +/* + * Common code for receive contexts interrupt handlers. + * Update traces, increment kernel IRQ counter and + * setup ASPM when needed. + */ +static void receive_interrupt_common(struct hfi2_ctxtdata *rcd) +{ + struct hfi2_devdata *dd = rcd->dd; + + trace_hfi2_receive_interrupt(dd, rcd); + this_cpu_inc(*dd->int_counter); +} + +/* + * __hfi2_rcd_eoi_intr() - Make HW issue receive interrupt + * when there are packets present in the queue. When calling + * with interrupts enabled please use hfi2_rcd_eoi_intr. + * + * @rcd: valid receive context + */ +static void __hfi2_rcd_eoi_intr(struct hfi2_ctxtdata *rcd) +{ + if (!rcd->rcvhdrq) + return; + clear_recv_intr(rcd); + if (check_packet_present(rcd)) + hfi2_force_recv_intr(rcd); +} + +/** + * hfi2_rcd_eoi_intr() - End of Interrupt processing action + * + * @rcd: Ptr to hfi2_ctxtdata of receive context + * + * Hold IRQs so we can safely clear the interrupt and + * recheck for a packet that may have arrived after the previous + * check and the interrupt clear. If a packet arrived, force another + * interrupt. This routine can be called at the end of receive packet + * processing in interrupt service routines, interrupt service thread + * and softirqs + */ +static void hfi2_rcd_eoi_intr(struct hfi2_ctxtdata *rcd) +{ + unsigned long flags; + + local_irq_save(flags); + __hfi2_rcd_eoi_intr(rcd); + local_irq_restore(flags); +} + +/** + * hfi2_netdev_rx_napi - napi poll function to move eoi inline + * @napi: pointer to napi object + * @budget: netdev budget + */ +int hfi2_netdev_rx_napi(struct napi_struct *napi, int budget) +{ + struct hfi2_netdev_rxq *rxq = + container_of(napi, struct hfi2_netdev_rxq, napi); + struct hfi2_ctxtdata *rcd = rxq->rcd; + int work_done = 0; + + work_done = rcd->do_interrupt(rcd, budget); + + if (work_done < budget) { + napi_complete_done(napi, work_done); + hfi2_rcd_eoi_intr(rcd); + } + + return work_done; +} + +/* Receive packet napi handler for netdevs AIP */ +irqreturn_t hfi2_receive_context_interrupt_napi(int irq, void *data) +{ + struct hfi2_ctxtdata *rcd = data; + + receive_interrupt_common(rcd); + + if (likely(rcd->napi)) { + if (likely(napi_schedule_prep(rcd->napi))) + __napi_schedule_irqoff(rcd->napi); + else + __hfi2_rcd_eoi_intr(rcd); + } else { + WARN_ONCE(1, "Napi IRQ handler without napi set up ctxt=%d\n", + rcd->ctxt); + __hfi2_rcd_eoi_intr(rcd); + } + + return IRQ_HANDLED; +} + +/* + * Receive packet IRQ handler. This routine expects to be on its own IRQ. + * This routine will try to handle packets immediately (latency), but if + * it finds too many, it will invoke the thread handler (bandwitdh). The + * chip receive interrupt is *not* cleared down until this or the thread (if + * invoked) is finished. The intent is to avoid extra interrupts while we + * are processing packets anyway. + */ +irqreturn_t hfi2_receive_context_interrupt(int irq, void *data) +{ + struct hfi2_ctxtdata *rcd = data; + int disposition; + + receive_interrupt_common(rcd); + + /* receive interrupt remains blocked while processing packets */ + disposition = rcd->do_interrupt(rcd, 0); + + /* + * Too many packets were seen while processing packets in this + * IRQ handler. Invoke the handler thread. The receive interrupt + * remains blocked. + */ + if (disposition == RCV_PKT_LIMIT) + return IRQ_WAKE_THREAD; + + __hfi2_rcd_eoi_intr(rcd); + return IRQ_HANDLED; +} + +/* + * Receive packet thread handler. This expects to be invoked with the + * receive interrupt still blocked. + */ +irqreturn_t hfi2_receive_context_thread(int irq, void *data) +{ + struct hfi2_ctxtdata *rcd = data; + + /* receive interrupt is still blocked from the IRQ handler */ + (void)rcd->do_interrupt(rcd, 1); + + hfi2_rcd_eoi_intr(rcd); + + return IRQ_HANDLED; +} + +/* ========================================================================= */ + +u32 hfi2_read_physical_state(struct hfi2_devdata *dd) +{ + u64 reg; + + reg = hfi2_read_csr(dd, DC_DC8051_STS_CUR_STATE); + return (reg >> DC_DC8051_STS_CUR_STATE_PORT_SHIFT) & + DC_DC8051_STS_CUR_STATE_PORT_MASK; +} + +u32 hfi2_read_logical_state(struct hfi2_devdata *dd) +{ + u64 reg; + + reg = hfi2_read_csr(dd, DCC_CFG_PORT_CONFIG); + return (reg >> DCC_CFG_PORT_CONFIG_LINK_STATE_SHIFT) & + DCC_CFG_PORT_CONFIG_LINK_STATE_MASK; +} + +static void set_logical_state(struct hfi2_devdata *dd, u32 chip_lstate) +{ + u64 reg; + + reg = hfi2_read_csr(dd, DCC_CFG_PORT_CONFIG); + /* clear current state, set new state */ + reg &= ~DCC_CFG_PORT_CONFIG_LINK_STATE_SMASK; + reg |= (u64)chip_lstate << DCC_CFG_PORT_CONFIG_LINK_STATE_SHIFT; + hfi2_write_csr(dd, DCC_CFG_PORT_CONFIG, reg); +} + +/* + * Use the 8051 to read a LCB CSR. + */ +static int read_lcb_via_8051(struct hfi2_devdata *dd, u32 addr, u64 *data) +{ + u32 regno; + int ret; + + /* register is an index of LCB registers: (offset - base) / 8 */ + regno = (addr - DC_LCB_CFG_RUN) >> 3; + ret = do_8051_command(dd, HCMD_READ_LCB_CSR, regno, data); + if (ret != HCMD_SUCCESS) + return -EBUSY; + return 0; +} + +/* + * Provide a cache for some of the LCB registers in case the LCB is + * unavailable. + * (The LCB is unavailable in certain link states, for example.) + */ +struct lcb_datum { + u32 off; + u64 val; +}; + +static struct lcb_datum lcb_cache[] = { + { DC_LCB_ERR_INFO_RX_REPLAY_CNT, 0 }, + { DC_LCB_ERR_INFO_SEQ_CRC_CNT, 0 }, + { DC_LCB_ERR_INFO_REINIT_FROM_PEER_CNT, 0 }, +}; + +static void update_lcb_cache(struct hfi2_pportdata *ppd) +{ + int i; + int ret; + u64 val; + + for (i = 0; i < ARRAY_SIZE(lcb_cache); i++) { + ret = hfi2_read_lcb_csr(ppd, lcb_cache[i].off, &val); + + /* Update if we get good data */ + if (likely(ret != -EBUSY)) + lcb_cache[i].val = val; + } +} + +static int read_lcb_cache(u32 off, u64 *val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(lcb_cache); i++) { + if (lcb_cache[i].off == off) { + *val = lcb_cache[i].val; + return 0; + } + } + + pr_warn("%s bad offset 0x%x\n", __func__, off); + return -1; +} + +/* + * Read an LCB CSR. Access may not be in host control, so check. + * Return 0 on success, -EBUSY on failure. + */ +int hfi2_read_lcb_csr(struct hfi2_pportdata *ppd, u32 addr, u64 *data) +{ + struct hfi2_devdata *dd = ppd->dd; + + /* if up, go through the 8051 for the value */ + if (ppd->host_link_state & HLS_UP) + return read_lcb_via_8051(dd, addr, data); + /* if going up or down, check the cache, otherwise, no access */ + if (ppd->host_link_state & (HLS_GOING_UP | HLS_GOING_OFFLINE)) { + if (read_lcb_cache(addr, data)) + return -EBUSY; + return 0; + } + + /* otherwise, host has access */ + *data = hfi2_read_csr(dd, addr); + return 0; +} + +/* + * Use the 8051 to write a LCB CSR. + */ +static int write_lcb_via_8051(struct hfi2_devdata *dd, u32 addr, u64 data) +{ + u32 regno; + int ret; + + /* register is an index of LCB registers: (offset - base) / 8 */ + regno = (addr - DC_LCB_CFG_RUN) >> 3; + ret = do_8051_command(dd, HCMD_WRITE_LCB_CSR, regno, &data); + if (ret != HCMD_SUCCESS) + return -EBUSY; + return 0; +} + +/* + * Write an LCB CSR. Access may not be in host control, so check. + * Return 0 on success, -EBUSY on failure. + */ +int hfi2_write_lcb_csr(struct hfi2_pportdata *ppd, u32 addr, u64 data) +{ + struct hfi2_devdata *dd = ppd->dd; + + /* if up, go through the 8051 for the value */ + if (ppd->host_link_state & HLS_UP) + return write_lcb_via_8051(dd, addr, data); + /* if going up or down, no access */ + if (ppd->host_link_state & (HLS_GOING_UP | HLS_GOING_OFFLINE)) + return -EBUSY; + /* otherwise, host has access */ + hfi2_write_csr(dd, addr, data); + return 0; +} + +/* + * Returns: + * < 0 = Linux error, not able to get access + * > 0 = 8051 command RETURN_CODE + */ +static int do_8051_command(struct hfi2_devdata *dd, u32 type, u64 in_data, + u64 *out_data) +{ + u64 reg, completed; + int return_code; + unsigned long timeout; + + hfi2_cdbg(DC8051, "type %d, data 0x%012llx", type, in_data); + + mutex_lock(&dd->dc8051_lock); + + /* We can't send any commands to the 8051 if it's in reset */ + if (dd->dc_shutdown) { + return_code = -ENODEV; + goto fail; + } + + /* + * If an 8051 host command timed out previously, then the 8051 is + * stuck. + * + * On first timeout, attempt to reset and restart the entire DC + * block (including 8051). (Is this too big of a hammer?) + * + * If the 8051 times out a second time, the reset did not bring it + * back to healthy life. In that case, fail any subsequent commands. + */ + if (dd->dc8051_timed_out) { + if (dd->dc8051_timed_out > 1) { + dd_dev_err( + dd, + "Previous 8051 host command timed out, skipping command %u\n", + type); + return_code = -ENXIO; + goto fail; + } + _dc_shutdown(dd); + _dc_start(dd); + } + + /* + * If there is no timeout, then the 8051 command interface is + * waiting for a command. + */ + + /* + * When writing a LCB CSR, out_data contains the full value to + * be written, while in_data contains the relative LCB + * address in 7:0. Do the work here, rather than the caller, + * of distrubting the write data to where it needs to go: + * + * Write data + * 39:00 -> in_data[47:8] + * 47:40 -> DC8051_CFG_EXT_DEV_0.RETURN_CODE + * 63:48 -> DC8051_CFG_EXT_DEV_0.RSP_DATA + */ + if (type == HCMD_WRITE_LCB_CSR) { + in_data |= ((*out_data) & 0xffffffffffull) << 8; + /* must preserve COMPLETED - it is tied to hardware */ + reg = hfi2_read_csr(dd, DC_DC8051_CFG_EXT_DEV_0); + reg &= DC_DC8051_CFG_EXT_DEV_0_COMPLETED_SMASK; + reg |= ((((*out_data) >> 40) & 0xff) + << DC_DC8051_CFG_EXT_DEV_0_RETURN_CODE_SHIFT) | + ((((*out_data) >> 48) & 0xffff) + << DC_DC8051_CFG_EXT_DEV_0_RSP_DATA_SHIFT); + hfi2_write_csr(dd, DC_DC8051_CFG_EXT_DEV_0, reg); + } + + /* + * Do two writes: the first to stabilize the type and req_data, the + * second to activate. + */ + reg = ((u64)type & DC_DC8051_CFG_HOST_CMD_0_REQ_TYPE_MASK) + << DC_DC8051_CFG_HOST_CMD_0_REQ_TYPE_SHIFT | + (in_data & DC_DC8051_CFG_HOST_CMD_0_REQ_DATA_MASK) + << DC_DC8051_CFG_HOST_CMD_0_REQ_DATA_SHIFT; + hfi2_write_csr(dd, DC_DC8051_CFG_HOST_CMD_0, reg); + reg |= DC_DC8051_CFG_HOST_CMD_0_REQ_NEW_SMASK; + hfi2_write_csr(dd, DC_DC8051_CFG_HOST_CMD_0, reg); + + /* wait for completion, alternate: interrupt */ + timeout = jiffies + msecs_to_jiffies(DC8051_COMMAND_TIMEOUT); + while (1) { + reg = hfi2_read_csr(dd, DC_DC8051_CFG_HOST_CMD_1); + completed = reg & DC_DC8051_CFG_HOST_CMD_1_COMPLETED_SMASK; + if (completed) + break; + if (time_after(jiffies, timeout)) { + dd->dc8051_timed_out++; + dd_dev_err(dd, "8051 host command %u timeout\n", type); + if (out_data) + *out_data = 0; + return_code = -ETIMEDOUT; + goto fail; + } + udelay(2); + } + + if (out_data) { + *out_data = (reg >> DC_DC8051_CFG_HOST_CMD_1_RSP_DATA_SHIFT) & + DC_DC8051_CFG_HOST_CMD_1_RSP_DATA_MASK; + if (type == HCMD_READ_LCB_CSR) { + /* top 16 bits are in a different register */ + *out_data |= + (hfi2_read_csr(dd, DC_DC8051_CFG_EXT_DEV_1) & + DC_DC8051_CFG_EXT_DEV_1_REQ_DATA_SMASK) + << (48 - + DC_DC8051_CFG_EXT_DEV_1_REQ_DATA_SHIFT); + } + } + return_code = (reg >> DC_DC8051_CFG_HOST_CMD_1_RETURN_CODE_SHIFT) & + DC_DC8051_CFG_HOST_CMD_1_RETURN_CODE_MASK; + dd->dc8051_timed_out = 0; + /* + * Clear command for next user. + */ + hfi2_write_csr(dd, DC_DC8051_CFG_HOST_CMD_0, 0); + +fail: + mutex_unlock(&dd->dc8051_lock); + return return_code; +} + +static int set_physical_link_state(struct hfi2_devdata *dd, u64 state) +{ + return do_8051_command(dd, HCMD_CHANGE_PHY_STATE, state, NULL); +} + +int hfi2_load_8051_config(struct hfi2_devdata *dd, u8 field_id, u8 lane_id, + u32 config_data) +{ + u64 data; + int ret; + + data = (u64)field_id << LOAD_DATA_FIELD_ID_SHIFT | + (u64)lane_id << LOAD_DATA_LANE_ID_SHIFT | + (u64)config_data << LOAD_DATA_DATA_SHIFT; + ret = do_8051_command(dd, HCMD_LOAD_CONFIG_DATA, data, NULL); + if (ret != HCMD_SUCCESS) { + dd_dev_err(dd, + "load 8051 config: field id %d, lane %d, err %d\n", + (int)field_id, (int)lane_id, ret); + } + return ret; +} + +/* + * Read the 8051 firmware "registers". Use the RAM directly. Always + * set the result, even on error. + * Return 0 on success, -errno on failure + */ +int hfi2_read_8051_config(struct hfi2_devdata *dd, u8 field_id, u8 lane_id, + u32 *result) +{ + u64 big_data; + u32 addr; + int ret; + + /* address start depends on the lane_id */ + if (lane_id < 4) + addr = (4 * NUM_GENERAL_FIELDS) + + (lane_id * 4 * NUM_LANE_FIELDS); + else + addr = 0; + addr += field_id * 4; + + /* read is in 8-byte chunks, hardware will truncate the address down */ + ret = hfi2_read_8051_data(dd, addr, 8, &big_data); + + if (ret == 0) { + /* extract the 4 bytes we want */ + if (addr & 0x4) + *result = (u32)(big_data >> 32); + else + *result = (u32)big_data; + } else { + *result = 0; + dd_dev_err(dd, "%s: direct read failed, lane %d, field %d!\n", + __func__, lane_id, field_id); + } + + return ret; +} + +static int write_vc_local_phy(struct hfi2_devdata *dd, u8 power_management, + u8 continuous) +{ + u32 frame; + + frame = continuous << CONTINIOUS_REMOTE_UPDATE_SUPPORT_SHIFT | + power_management << POWER_MANAGEMENT_SHIFT; + return hfi2_load_8051_config(dd, VERIFY_CAP_LOCAL_PHY, GENERAL_CONFIG, + frame); +} + +static int write_vc_local_fabric(struct hfi2_devdata *dd, u8 vau, u8 z, u8 vcu, + u16 vl15buf, u8 crc_sizes) +{ + u32 frame; + + frame = (u32)vau << VAU_SHIFT | (u32)z << Z_SHIFT | + (u32)vcu << VCU_SHIFT | (u32)vl15buf << VL15BUF_SHIFT | + (u32)crc_sizes << CRC_SIZES_SHIFT; + return hfi2_load_8051_config(dd, VERIFY_CAP_LOCAL_FABRIC, + GENERAL_CONFIG, frame); +} + +static void read_vc_local_link_mode(struct hfi2_devdata *dd, u8 *misc_bits, + u8 *flag_bits, u16 *link_widths) +{ + u32 frame; + + hfi2_read_8051_config(dd, VERIFY_CAP_LOCAL_LINK_MODE, GENERAL_CONFIG, + &frame); + *misc_bits = (frame >> MISC_CONFIG_BITS_SHIFT) & MISC_CONFIG_BITS_MASK; + *flag_bits = (frame >> LOCAL_FLAG_BITS_SHIFT) & LOCAL_FLAG_BITS_MASK; + *link_widths = (frame >> LINK_WIDTH_SHIFT) & LINK_WIDTH_MASK; +} + +static int write_vc_local_link_mode(struct hfi2_devdata *dd, u8 misc_bits, + u8 flag_bits, u16 link_widths) +{ + u32 frame; + + frame = (u32)misc_bits << MISC_CONFIG_BITS_SHIFT | + (u32)flag_bits << LOCAL_FLAG_BITS_SHIFT | + (u32)link_widths << LINK_WIDTH_SHIFT; + return hfi2_load_8051_config(dd, VERIFY_CAP_LOCAL_LINK_MODE, + GENERAL_CONFIG, frame); +} + +static int write_local_device_id(struct hfi2_devdata *dd, u16 device_id, + u8 device_rev) +{ + u32 frame; + + frame = ((u32)device_id << LOCAL_DEVICE_ID_SHIFT) | + ((u32)device_rev << LOCAL_DEVICE_REV_SHIFT); + return hfi2_load_8051_config(dd, LOCAL_DEVICE_ID, GENERAL_CONFIG, + frame); +} + +static void read_remote_device_id(struct hfi2_devdata *dd, u16 *device_id, + u8 *device_rev) +{ + u32 frame; + + hfi2_read_8051_config(dd, REMOTE_DEVICE_ID, GENERAL_CONFIG, &frame); + *device_id = (frame >> REMOTE_DEVICE_ID_SHIFT) & REMOTE_DEVICE_ID_MASK; + *device_rev = (frame >> REMOTE_DEVICE_REV_SHIFT) & + REMOTE_DEVICE_REV_MASK; +} + +int hfi2_write_host_interface_version(struct hfi2_devdata *dd, u8 version) +{ + u32 frame; + u32 mask; + + mask = (HOST_INTERFACE_VERSION_MASK << HOST_INTERFACE_VERSION_SHIFT); + hfi2_read_8051_config(dd, RESERVED_REGISTERS, GENERAL_CONFIG, &frame); + /* Clear, then set field */ + frame &= ~mask; + frame |= ((u32)version << HOST_INTERFACE_VERSION_SHIFT); + return hfi2_load_8051_config(dd, RESERVED_REGISTERS, GENERAL_CONFIG, + frame); +} + +void hfi2_read_misc_status(struct hfi2_devdata *dd, u8 *ver_major, + u8 *ver_minor, u8 *ver_patch) +{ + u32 frame; + + hfi2_read_8051_config(dd, MISC_STATUS, GENERAL_CONFIG, &frame); + *ver_major = (frame >> STS_FM_VERSION_MAJOR_SHIFT) & + STS_FM_VERSION_MAJOR_MASK; + *ver_minor = (frame >> STS_FM_VERSION_MINOR_SHIFT) & + STS_FM_VERSION_MINOR_MASK; + + hfi2_read_8051_config(dd, VERSION_PATCH, GENERAL_CONFIG, &frame); + *ver_patch = (frame >> STS_FM_VERSION_PATCH_SHIFT) & + STS_FM_VERSION_PATCH_MASK; +} + +static void read_vc_remote_phy(struct hfi2_devdata *dd, u8 *power_management, + u8 *continuous) +{ + u32 frame; + + hfi2_read_8051_config(dd, VERIFY_CAP_REMOTE_PHY, GENERAL_CONFIG, + &frame); + *power_management = (frame >> POWER_MANAGEMENT_SHIFT) & + POWER_MANAGEMENT_MASK; + *continuous = (frame >> CONTINIOUS_REMOTE_UPDATE_SUPPORT_SHIFT) & + CONTINIOUS_REMOTE_UPDATE_SUPPORT_MASK; +} + +static void read_vc_remote_fabric(struct hfi2_devdata *dd, u8 *vau, u8 *z, + u8 *vcu, u16 *vl15buf, u8 *crc_sizes) +{ + u32 frame; + + hfi2_read_8051_config(dd, VERIFY_CAP_REMOTE_FABRIC, GENERAL_CONFIG, + &frame); + *vau = (frame >> VAU_SHIFT) & VAU_MASK; + *z = (frame >> Z_SHIFT) & Z_MASK; + *vcu = (frame >> VCU_SHIFT) & VCU_MASK; + *vl15buf = (frame >> VL15BUF_SHIFT) & VL15BUF_MASK; + *crc_sizes = (frame >> CRC_SIZES_SHIFT) & CRC_SIZES_MASK; +} + +static void read_vc_remote_link_width(struct hfi2_devdata *dd, + u8 *remote_tx_rate, u16 *link_widths) +{ + u32 frame; + + hfi2_read_8051_config(dd, VERIFY_CAP_REMOTE_LINK_WIDTH, GENERAL_CONFIG, + &frame); + *remote_tx_rate = (frame >> REMOTE_TX_RATE_SHIFT) & REMOTE_TX_RATE_MASK; + *link_widths = (frame >> LINK_WIDTH_SHIFT) & LINK_WIDTH_MASK; +} + +static void read_local_lni(struct hfi2_devdata *dd, u8 *enable_lane_rx) +{ + u32 frame; + + hfi2_read_8051_config(dd, LOCAL_LNI_INFO, GENERAL_CONFIG, &frame); + *enable_lane_rx = (frame >> ENABLE_LANE_RX_SHIFT) & ENABLE_LANE_RX_MASK; +} + +static void read_last_local_state(struct hfi2_devdata *dd, u32 *lls) +{ + hfi2_read_8051_config(dd, LAST_LOCAL_STATE_COMPLETE, GENERAL_CONFIG, + lls); +} + +static void read_last_remote_state(struct hfi2_devdata *dd, u32 *lrs) +{ + hfi2_read_8051_config(dd, LAST_REMOTE_STATE_COMPLETE, GENERAL_CONFIG, + lrs); +} + +void hfi2_wfr_read_link_quality(struct hfi2_pportdata *ppd, u8 *link_quality) +{ + struct hfi2_devdata *dd = ppd->dd; + u32 frame; + int ret; + + *link_quality = 0; + if (ppd->host_link_state & HLS_UP) { + ret = hfi2_read_8051_config(dd, LINK_QUALITY_INFO, + GENERAL_CONFIG, &frame); + if (ret == 0) + *link_quality = (frame >> LINK_QUALITY_SHIFT) & + LINK_QUALITY_MASK; + } +} + +static void read_planned_down_reason_code(struct hfi2_devdata *dd, u8 *pdrrc) +{ + u32 frame; + + hfi2_read_8051_config(dd, LINK_QUALITY_INFO, GENERAL_CONFIG, &frame); + *pdrrc = (frame >> DOWN_REMOTE_REASON_SHIFT) & DOWN_REMOTE_REASON_MASK; +} + +static void read_link_down_reason(struct hfi2_devdata *dd, u8 *ldr) +{ + u32 frame; + + hfi2_read_8051_config(dd, LINK_DOWN_REASON, GENERAL_CONFIG, &frame); + *ldr = (frame & 0xff); +} + +static int read_tx_settings(struct hfi2_devdata *dd, u8 *enable_lane_tx, + u8 *tx_polarity_inversion, + u8 *rx_polarity_inversion, u8 *max_rate) +{ + u32 frame; + int ret; + + ret = hfi2_read_8051_config(dd, TX_SETTINGS, GENERAL_CONFIG, &frame); + *enable_lane_tx = (frame >> ENABLE_LANE_TX_SHIFT) & ENABLE_LANE_TX_MASK; + *tx_polarity_inversion = (frame >> TX_POLARITY_INVERSION_SHIFT) & + TX_POLARITY_INVERSION_MASK; + *rx_polarity_inversion = (frame >> RX_POLARITY_INVERSION_SHIFT) & + RX_POLARITY_INVERSION_MASK; + *max_rate = (frame >> MAX_RATE_SHIFT) & MAX_RATE_MASK; + return ret; +} + +static int write_tx_settings(struct hfi2_devdata *dd, u8 enable_lane_tx, + u8 tx_polarity_inversion, u8 rx_polarity_inversion, + u8 max_rate) +{ + u32 frame; + + /* no need to mask, all variable sizes match field widths */ + frame = enable_lane_tx << ENABLE_LANE_TX_SHIFT | + tx_polarity_inversion << TX_POLARITY_INVERSION_SHIFT | + rx_polarity_inversion << RX_POLARITY_INVERSION_SHIFT | + max_rate << MAX_RATE_SHIFT; + return hfi2_load_8051_config(dd, TX_SETTINGS, GENERAL_CONFIG, frame); +} + +/* + * Read an idle LCB message. + * + * Returns 0 on success, -EINVAL on error + */ +static int read_idle_message(struct hfi2_devdata *dd, u64 type, u64 *data_out) +{ + int ret; + + ret = do_8051_command(dd, HCMD_READ_LCB_IDLE_MSG, type, data_out); + if (ret != HCMD_SUCCESS) { + dd_dev_err(dd, "read idle message: type %d, err %d\n", + (u32)type, ret); + return -EINVAL; + } + dd_dev_info(dd, "%s: read idle message 0x%llx\n", __func__, *data_out); + /* return only the payload as we already know the type */ + *data_out >>= IDLE_PAYLOAD_SHIFT; + return 0; +} + +/* + * Read an idle SMA message. To be done in response to a notification from + * the 8051. + * + * Returns 0 on success, -EINVAL on error + */ +static int read_idle_sma(struct hfi2_devdata *dd, u64 *data) +{ + return read_idle_message(dd, (u64)IDLE_SMA << IDLE_MSG_TYPE_SHIFT, + data); +} + +/* + * Send an idle LCB message. + * + * Returns 0 on success, -EINVAL on error + */ +static int send_idle_message(struct hfi2_devdata *dd, u64 data) +{ + int ret; + + dd_dev_info(dd, "%s: sending idle message 0x%llx\n", __func__, data); + ret = do_8051_command(dd, HCMD_SEND_LCB_IDLE_MSG, data, NULL); + if (ret != HCMD_SUCCESS) { + dd_dev_err(dd, "send idle message: data 0x%llx, err %d\n", data, + ret); + return -EINVAL; + } + return 0; +} + +/* + * Send an idle SMA message. + * + * Returns 0 on success, -EINVAL on error + */ +int hfi2_send_idle_sma(struct hfi2_devdata *dd, u64 message) +{ + u64 data; + + data = ((message & IDLE_PAYLOAD_MASK) << IDLE_PAYLOAD_SHIFT) | + ((u64)IDLE_SMA << IDLE_MSG_TYPE_SHIFT); + return send_idle_message(dd, data); +} + +/* + * Initialize the LCB then do a quick link up. This may or may not be + * in hfi2_loopback. + * + * return 0 on success, -errno on error + */ +static int do_quick_linkup(struct hfi2_devdata *dd) +{ + int ret; + + lcb_shutdown(dd, 0); + + if (hfi2_loopback) { + /* LCB_CFG_LOOPBACK.VAL = 2 */ + /* LCB_CFG_LANE_WIDTH.VAL = 0 */ + hfi2_write_csr(dd, DC_LCB_CFG_LOOPBACK, + 2ull << DC_LCB_CFG_LOOPBACK_VAL_SHIFT); + hfi2_write_csr(dd, DC_LCB_CFG_LANE_WIDTH, 0); + } + + /* start the LCBs */ + /* LCB_CFG_TX_FIFOS_RESET.VAL = 0 */ + hfi2_write_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET, 0); + + if (!hfi2_loopback) { + /* + * When doing quick linkup and not in hfi2_loopback, both + * sides must be done with LCB set-up before either + * starts the quick linkup. Put a delay here so that + * both sides can be started and have a chance to be + * done with LCB set up before resuming. + */ + dd_dev_err(dd, + "Pausing for peer to be finished with LCB set up\n"); + msleep(5000); + dd_dev_err(dd, "Continuing with quick linkup\n"); + } + + hfi2_write_csr(dd, DC_LCB_ERR_EN, 0); /* mask LCB errors */ + set_8051_lcb_access(dd); + + /* + * State "quick" LinkUp request sets the physical link state to + * LinkUp without a verify capability sequence. + * This state is in simulator v37 and later. + */ + ret = set_physical_link_state(dd, PLS_QUICK_LINKUP); + if (ret != HCMD_SUCCESS) { + dd_dev_err( + dd, + "%s: set physical link state to quick LinkUp failed with return %d\n", + __func__, ret); + + set_host_lcb_access(dd); + hfi2_write_csr(dd, DC_LCB_ERR_EN, ~0ull); /* watch LCB errors */ + + if (ret >= 0) + ret = -EINVAL; + return ret; + } + + return 0; /* success */ +} + +/* + * Do all special steps to set up hfi2_loopback. + */ +static int init_loopback(struct hfi2_devdata *dd) +{ + dd_dev_info(dd, "Entering hfi2_loopback mode\n"); + + /* all loopbacks should disable self GUID check */ + hfi2_write_csr(dd, DC_DC8051_CFG_MODE, + (hfi2_read_csr(dd, DC_DC8051_CFG_MODE) | + DISABLE_SELF_GUID_CHECK)); + + /* + * The simulator has only one hfi2_loopback option - LCB. Switch + * to that option, which includes quick link up. + * + * Accept all valid hfi2_loopback values. + */ + if ((dd->icode == ICODE_FUNCTIONAL_SIMULATOR) && + (hfi2_loopback == LOOPBACK_SERDES || + hfi2_loopback == LOOPBACK_LCB || + hfi2_loopback == LOOPBACK_CABLE)) { + hfi2_loopback = LOOPBACK_LCB; + hfi2_quick_linkup = 1; + return 0; + } + + /* + * SerDes hfi2_loopback init sequence is handled in set_local_link_attributes + */ + if (hfi2_loopback == LOOPBACK_SERDES) + return 0; + + /* LCB hfi2_loopback - handled at poll time */ + if (hfi2_loopback == LOOPBACK_LCB) { + hfi2_quick_linkup = 1; /* LCB is always quick linkup */ + return 0; + } + + /* external cable hfi2_loopback requires no extra steps */ + if (hfi2_loopback == LOOPBACK_CABLE) + return 0; + + dd_dev_err(dd, "Invalid hfi2_loopback mode %d\n", hfi2_loopback); + return -EINVAL; +} + +/* + * Translate from the OPA_LINK_WIDTH handed to us by the FM to bits + * used in the Verify Capability link width attribute. + */ +static u16 opa_to_vc_link_widths(u16 opa_widths) +{ + int i; + u16 result = 0; + + static const struct link_bits { + u16 from; + u16 to; + } opa_link_xlate[] = { + { OPA_LINK_WIDTH_1X, 1 << (1 - 1) }, + { OPA_LINK_WIDTH_2X, 1 << (2 - 1) }, + { OPA_LINK_WIDTH_3X, 1 << (3 - 1) }, + { OPA_LINK_WIDTH_4X, 1 << (4 - 1) }, + }; + + for (i = 0; i < ARRAY_SIZE(opa_link_xlate); i++) { + if (opa_widths & opa_link_xlate[i].from) + result |= opa_link_xlate[i].to; + } + return result; +} + +/* + * Set link attributes before moving to polling. + */ +static int set_local_link_attributes(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u8 enable_lane_tx; + u8 tx_polarity_inversion; + u8 rx_polarity_inversion; + int ret; + u32 misc_bits = 0; + /* reset our fabric serdes to clear any lingering problems */ + hfi2_fabric_serdes_reset(dd); + + /* set the local tx rate - need to read-modify-write */ + ret = read_tx_settings(dd, &enable_lane_tx, &tx_polarity_inversion, + &rx_polarity_inversion, &ppd->local_tx_rate); + if (ret) + goto set_local_link_attributes_fail; + + if (dd->dc8051_ver < dc8051_ver(0, 20, 0)) { + /* set the tx rate to the fastest enabled */ + if (ppd->link_speed_enabled & OPA_LINK_SPEED_25G) + ppd->local_tx_rate = 1; + else + ppd->local_tx_rate = 0; + } else { + /* set the tx rate to all enabled */ + ppd->local_tx_rate = 0; + if (ppd->link_speed_enabled & OPA_LINK_SPEED_25G) + ppd->local_tx_rate |= 2; + if (ppd->link_speed_enabled & OPA_LINK_SPEED_12_5G) + ppd->local_tx_rate |= 1; + } + + enable_lane_tx = 0xF; /* enable all four lanes */ + ret = write_tx_settings(dd, enable_lane_tx, tx_polarity_inversion, + rx_polarity_inversion, ppd->local_tx_rate); + if (ret != HCMD_SUCCESS) + goto set_local_link_attributes_fail; + + ret = hfi2_write_host_interface_version(dd, HOST_INTERFACE_VERSION); + if (ret != HCMD_SUCCESS) { + ppd_dev_err( + ppd, + "Failed to set host interface version, return 0x%x\n", + ret); + goto set_local_link_attributes_fail; + } + + /* + * DC supports continuous updates. + */ + ret = write_vc_local_phy(dd, 0 /* no power management */, + 1 /* continuous updates */); + if (ret != HCMD_SUCCESS) + goto set_local_link_attributes_fail; + + /* z=1 in the next call: AU of 0 is not supported by the hardware */ + ret = write_vc_local_fabric(dd, dd->vau, 1, dd->vcu, dd->vl15_init, + ppd->port_crc_mode_enabled); + if (ret != HCMD_SUCCESS) + goto set_local_link_attributes_fail; + + /* + * SerDes hfi2_loopback init sequence requires + * setting bit 0 of MISC_CONFIG_BITS + */ + if (hfi2_loopback == LOOPBACK_SERDES) + misc_bits |= 1 << LOOPBACK_SERDES_CONFIG_BIT_MASK_SHIFT; + + /* + * An external device configuration request is used to reset the LCB + * to retry to obtain operational lanes when the first attempt is + * unsuccesful. + */ + if (dd->dc8051_ver >= dc8051_ver(1, 25, 0)) + misc_bits |= 1 << EXT_CFG_LCB_RESET_SUPPORTED_SHIFT; + + ret = write_vc_local_link_mode( + dd, misc_bits, 0, + opa_to_vc_link_widths(ppd->link_width_enabled)); + if (ret != HCMD_SUCCESS) + goto set_local_link_attributes_fail; + + /* let peer know who we are */ + ret = write_local_device_id(dd, dd->pcidev->device, dd->minrev); + if (ret == HCMD_SUCCESS) + return 0; + +set_local_link_attributes_fail: + ppd_dev_err(ppd, "Failed to set local link attributes, return 0x%x\n", + ret); + return ret; +} + +/* + * Call this to start the link. + * Do not do anything if the link is disabled. + * Returns 0 if link is disabled, moved to polling, or the driver is not ready. + */ +int hfi2_start_link(struct hfi2_pportdata *ppd) +{ + /* + * Tune the SerDes to a ballpark setting for optimal signal and bit + * error rate. Needs to be done before starting the link. + */ + hfi2_tune_serdes(ppd); + + if (!ppd->driver_link_ready) { + ppd_dev_info( + ppd, + "%s: stopping link start because driver is not ready\n", + __func__); + return 0; + } + + /* + * FULL_MGMT_P_KEY is cleared from the pkey table, so that the + * pkey table can be configured properly if the HFI unit is connected + * to switch port with MgmtAllowed=NO + */ + clear_full_mgmt_pkey(ppd); + + return hfi2_set_link_state(ppd, HLS_DN_POLL); +} + +static void wait_for_qsfp_init(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 mask; + unsigned long timeout; + + /* + * Some QSFP cables have a quirk that asserts the IntN line as a side + * effect of power up on plug-in. We ignore this false positive + * interrupt until the module has finished powering up by waiting for + * a minimum timeout of the module inrush initialization time of + * 500 ms (SFF 8679 Table 5-6) to ensure the voltage rails in the + * module have stabilized. + */ + msleep(500); + + /* + * Check for QSFP interrupt for t_init (SFF 8679 Table 8-1) + */ + timeout = jiffies + msecs_to_jiffies(2000); + while (1) { + mask = hfi2_read_csr(dd, dd->hfi2_id ? ASIC_QSFP2_IN : + ASIC_QSFP1_IN); + if (!(mask & QSFP_HFI0_INT_N)) + break; + if (time_after(jiffies, timeout)) { + ppd_dev_info(ppd, + "%s: No IntN detected, reset complete\n", + __func__); + break; + } + udelay(2); + } +} + +static void set_qsfp_int_n(struct hfi2_pportdata *ppd, u8 enable) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 mask; + + mask = hfi2_read_csr(dd, + dd->hfi2_id ? ASIC_QSFP2_MASK : ASIC_QSFP1_MASK); + if (enable) { + /* + * Clear the status register to avoid an immediate interrupt + * when we re-enable the IntN pin + */ + hfi2_write_csr( + dd, dd->hfi2_id ? ASIC_QSFP2_CLEAR : ASIC_QSFP1_CLEAR, + QSFP_HFI0_INT_N); + mask |= (u64)QSFP_HFI0_INT_N; + } else { + mask &= ~(u64)QSFP_HFI0_INT_N; + } + hfi2_write_csr(dd, dd->hfi2_id ? ASIC_QSFP2_MASK : ASIC_QSFP1_MASK, + mask); +} + +int hfi2_reset_qsfp(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 mask, qsfp_mask; + + /* Disable INT_N from triggering QSFP interrupts */ + set_qsfp_int_n(ppd, 0); + + /* Reset the QSFP */ + mask = (u64)QSFP_HFI0_RESET_N; + + qsfp_mask = hfi2_read_csr(dd, dd->hfi2_id ? ASIC_QSFP2_OUT : + ASIC_QSFP1_OUT); + qsfp_mask &= ~mask; + hfi2_write_csr(dd, dd->hfi2_id ? ASIC_QSFP2_OUT : ASIC_QSFP1_OUT, + qsfp_mask); + + udelay(10); + + qsfp_mask |= mask; + hfi2_write_csr(dd, dd->hfi2_id ? ASIC_QSFP2_OUT : ASIC_QSFP1_OUT, + qsfp_mask); + + wait_for_qsfp_init(ppd); + + /* + * Allow INT_N to trigger the QSFP interrupt to watch + * for alarms and warnings + */ + set_qsfp_int_n(ppd, 1); + + /* + * After the reset, AOC transmitters are enabled by default. They need + * to be turned off to complete the QSFP setup before they can be + * enabled again. + */ + return hfi2_set_qsfp_tx(ppd, 0); +} + +static int handle_qsfp_error_conditions(struct hfi2_pportdata *ppd, + u8 *qsfp_interrupt_status) +{ + if ((qsfp_interrupt_status[0] & QSFP_HIGH_TEMP_ALARM) || + (qsfp_interrupt_status[0] & QSFP_HIGH_TEMP_WARNING)) + ppd_dev_err(ppd, "%s: QSFP cable temperature too high\n", + __func__); + + if ((qsfp_interrupt_status[0] & QSFP_LOW_TEMP_ALARM) || + (qsfp_interrupt_status[0] & QSFP_LOW_TEMP_WARNING)) + ppd_dev_err(ppd, "%s: QSFP cable temperature too low\n", + __func__); + + /* + * The remaining alarms/warnings don't matter if the link is down. + */ + if (ppd->host_link_state & HLS_DOWN) + return 0; + + if ((qsfp_interrupt_status[1] & QSFP_HIGH_VCC_ALARM) || + (qsfp_interrupt_status[1] & QSFP_HIGH_VCC_WARNING)) + ppd_dev_err(ppd, "%s: QSFP supply voltage too high\n", + __func__); + + if ((qsfp_interrupt_status[1] & QSFP_LOW_VCC_ALARM) || + (qsfp_interrupt_status[1] & QSFP_LOW_VCC_WARNING)) + ppd_dev_err(ppd, "%s: QSFP supply voltage too low\n", __func__); + + /* Byte 2 is vendor specific */ + + if ((qsfp_interrupt_status[3] & QSFP_HIGH_POWER_ALARM) || + (qsfp_interrupt_status[3] & QSFP_HIGH_POWER_WARNING)) + ppd_dev_err(ppd, "%s: Cable RX channel 1/2 power too high\n", + __func__); + + if ((qsfp_interrupt_status[3] & QSFP_LOW_POWER_ALARM) || + (qsfp_interrupt_status[3] & QSFP_LOW_POWER_WARNING)) + ppd_dev_err(ppd, "%s: Cable RX channel 1/2 power too low\n", + __func__); + + if ((qsfp_interrupt_status[4] & QSFP_HIGH_POWER_ALARM) || + (qsfp_interrupt_status[4] & QSFP_HIGH_POWER_WARNING)) + ppd_dev_err(ppd, "%s: Cable RX channel 3/4 power too high\n", + __func__); + + if ((qsfp_interrupt_status[4] & QSFP_LOW_POWER_ALARM) || + (qsfp_interrupt_status[4] & QSFP_LOW_POWER_WARNING)) + ppd_dev_err(ppd, "%s: Cable RX channel 3/4 power too low\n", + __func__); + + if ((qsfp_interrupt_status[5] & QSFP_HIGH_BIAS_ALARM) || + (qsfp_interrupt_status[5] & QSFP_HIGH_BIAS_WARNING)) + ppd_dev_err(ppd, "%s: Cable TX channel 1/2 bias too high\n", + __func__); + + if ((qsfp_interrupt_status[5] & QSFP_LOW_BIAS_ALARM) || + (qsfp_interrupt_status[5] & QSFP_LOW_BIAS_WARNING)) + ppd_dev_err(ppd, "%s: Cable TX channel 1/2 bias too low\n", + __func__); + + if ((qsfp_interrupt_status[6] & QSFP_HIGH_BIAS_ALARM) || + (qsfp_interrupt_status[6] & QSFP_HIGH_BIAS_WARNING)) + ppd_dev_err(ppd, "%s: Cable TX channel 3/4 bias too high\n", + __func__); + + if ((qsfp_interrupt_status[6] & QSFP_LOW_BIAS_ALARM) || + (qsfp_interrupt_status[6] & QSFP_LOW_BIAS_WARNING)) + ppd_dev_err(ppd, "%s: Cable TX channel 3/4 bias too low\n", + __func__); + + if ((qsfp_interrupt_status[7] & QSFP_HIGH_POWER_ALARM) || + (qsfp_interrupt_status[7] & QSFP_HIGH_POWER_WARNING)) + ppd_dev_err(ppd, "%s: Cable TX channel 1/2 power too high\n", + __func__); + + if ((qsfp_interrupt_status[7] & QSFP_LOW_POWER_ALARM) || + (qsfp_interrupt_status[7] & QSFP_LOW_POWER_WARNING)) + ppd_dev_err(ppd, "%s: Cable TX channel 1/2 power too low\n", + __func__); + + if ((qsfp_interrupt_status[8] & QSFP_HIGH_POWER_ALARM) || + (qsfp_interrupt_status[8] & QSFP_HIGH_POWER_WARNING)) + ppd_dev_err(ppd, "%s: Cable TX channel 3/4 power too high\n", + __func__); + + if ((qsfp_interrupt_status[8] & QSFP_LOW_POWER_ALARM) || + (qsfp_interrupt_status[8] & QSFP_LOW_POWER_WARNING)) + ppd_dev_err(ppd, "%s: Cable TX channel 3/4 power too low\n", + __func__); + + /* Bytes 9-10 and 11-12 are reserved */ + /* Bytes 13-15 are vendor specific */ + + return 0; +} + +/* This routine will only be scheduled if the QSFP module present is asserted */ +void hfi2_qsfp_event(struct work_struct *work) +{ + struct qsfp_data *qd; + struct hfi2_pportdata *ppd; + struct hfi2_devdata *dd; + + qd = container_of(work, struct qsfp_data, qsfp_work); + ppd = qd->ppd; + dd = ppd->dd; + + /* Sanity check */ + if (!hfi2_qsfp_mod_present(ppd)) + return; + + if (ppd->host_link_state == HLS_DN_DISABLE) { + ppd_dev_info( + ppd, + "%s: stopping link start because link is disabled\n", + __func__); + return; + } + + /* + * Turn DC back on after cable has been re-inserted. Up until + * now, the DC has been in reset to save power. + */ + dc_start(dd); + + if (qd->cache_refresh_required) { + set_qsfp_int_n(ppd, 0); + + wait_for_qsfp_init(ppd); + + /* + * Allow INT_N to trigger the QSFP interrupt to watch + * for alarms and warnings + */ + set_qsfp_int_n(ppd, 1); + + hfi2_start_link(ppd); + } + + if (qd->check_interrupt_flags) { + u8 qsfp_interrupt_status[16] = { + 0, + }; + + if (hfi2_one_qsfp_read(ppd, dd->hfi2_id, 6, + &qsfp_interrupt_status[0], 16) != 16) { + ppd_dev_info( + ppd, + "%s: Failed to read status of QSFP module\n", + __func__); + } else { + unsigned long flags; + + handle_qsfp_error_conditions(ppd, + qsfp_interrupt_status); + spin_lock_irqsave(&ppd->qsfp_info.qsfp_lock, flags); + ppd->qsfp_info.check_interrupt_flags = 0; + spin_unlock_irqrestore(&ppd->qsfp_info.qsfp_lock, + flags); + } + } +} + +void hfi2_init_qsfp_int(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 qsfp_mask; + + qsfp_mask = (u64)(QSFP_HFI0_INT_N | QSFP_HFI0_MODPRST_N); + /* Clear current status to avoid spurious interrupts */ + hfi2_write_csr(dd, dd->hfi2_id ? ASIC_QSFP2_CLEAR : ASIC_QSFP1_CLEAR, + qsfp_mask); + hfi2_write_csr(dd, dd->hfi2_id ? ASIC_QSFP2_MASK : ASIC_QSFP1_MASK, + qsfp_mask); + + set_qsfp_int_n(ppd, 0); + + /* Handle active low nature of INT_N and MODPRST_N pins */ + if (hfi2_qsfp_mod_present(ppd)) + qsfp_mask &= ~(u64)QSFP_HFI0_MODPRST_N; + hfi2_write_csr(dd, dd->hfi2_id ? ASIC_QSFP2_INVERT : ASIC_QSFP1_INVERT, + qsfp_mask); + + /* Enable the appropriate QSFP IRQ source */ + if (!dd->hfi2_id) + hfi2_set_intr_bits(dd, QSFP1_INT, QSFP1_INT, true); + else + hfi2_set_intr_bits(dd, QSFP2_INT, QSFP2_INT, true); +} + +/* + * Do a one-time initialize of the LCB block. + */ +static void init_lcb(struct hfi2_devdata *dd) +{ + /* the DC has been reset earlier in the driver load */ + + /* set LCB for cclk hfi2_loopback on the port */ + hfi2_write_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET, 0x01); + hfi2_write_csr(dd, DC_LCB_CFG_LANE_WIDTH, 0x00); + hfi2_write_csr(dd, DC_LCB_CFG_REINIT_AS_SLAVE, 0x00); + hfi2_write_csr(dd, DC_LCB_CFG_CNT_FOR_SKIP_STALL, 0x110); + hfi2_write_csr(dd, DC_LCB_CFG_CLK_CNTR, 0x08); + hfi2_write_csr(dd, DC_LCB_CFG_LOOPBACK, 0x02); + hfi2_write_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET, 0x00); +} + +/* + * Perform a test read on the QSFP. Return 0 on success, -ERRNO + * on error. + */ +static int test_qsfp_read(struct hfi2_pportdata *ppd) +{ + int ret; + u8 status; + + /* + * Report success if not a QSFP or, if it is a QSFP, but the cable is + * not present + */ + if (ppd->port_type != PORT_TYPE_QSFP || !hfi2_qsfp_mod_present(ppd)) + return 0; + + /* read byte 2, the status byte */ + ret = hfi2_one_qsfp_read(ppd, ppd->dd->hfi2_id, 2, &status, 1); + if (ret < 0) + return ret; + if (ret != 1) + return -EIO; + + return 0; /* success */ +} + +/* + * Values for QSFP retry. + * + * Give up after 10s (20 x 500ms). The overall timeout was empirically + * arrived at from experience on a large cluster. + */ +#define MAX_QSFP_RETRIES 20 +#define QSFP_RETRY_WAIT 500 /* msec */ + +/* + * Try a QSFP read. If it fails, schedule a retry for later. + * Called on first link activation after driver load. + */ +static void try_start_link(struct hfi2_pportdata *ppd) +{ + if (test_qsfp_read(ppd)) { + /* read failed */ + if (ppd->qsfp_retry_count >= MAX_QSFP_RETRIES) { + ppd_dev_err(ppd, "QSFP not responding, giving up\n"); + return; + } + ppd_dev_info(ppd, + "QSFP not responding, waiting and retrying %d\n", + (int)ppd->qsfp_retry_count); + ppd->qsfp_retry_count++; + queue_delayed_work(ppd->link_wq, &ppd->start_link_work, + msecs_to_jiffies(QSFP_RETRY_WAIT)); + return; + } + ppd->qsfp_retry_count = 0; + + hfi2_start_link(ppd); +} + +/* + * Workqueue function to start the link after a delay. + */ +void hfi2_handle_start_link(struct work_struct *work) +{ + struct hfi2_pportdata *ppd = + container_of(work, struct hfi2_pportdata, start_link_work.work); + try_start_link(ppd); +} + +int hfi2_bringup_serdes(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 guid; + int ret; + + if (HFI2_CAP_IS_KSET(EXTENDED_PSN)) + add_rcvctrl(ppd, RCV_CTRL_RCV_EXTENDED_PSN_ENABLE_SMASK); + + guid = ppd->guids[HFI2_PORT_GUID_INDEX]; + if (!guid) { + /* OPA spec says bits 34:32 are port number, 1-7 */ + if (dd->base_guid) + guid = (dd->base_guid & ~(7ULL << 32)) | + ((u64)ppd->port << 32); + ppd->guids[HFI2_PORT_GUID_INDEX] = guid; + } + + /* Set linkinit_reason on power up per OPA spec */ + ppd->linkinit_reason = OPA_LINKINIT_REASON_LINKUP; + + /* one-time init of the LCB */ + init_lcb(dd); + + if (hfi2_loopback) { + ret = init_loopback(dd); + if (ret < 0) + return ret; + } + + hfi2_get_port_type(ppd); + if (ppd->port_type == PORT_TYPE_QSFP) { + set_qsfp_int_n(ppd, 0); + wait_for_qsfp_init(ppd); + set_qsfp_int_n(ppd, 1); + } + + try_start_link(ppd); + return 0; +} + +void hfi2_quiet_serdes(struct hfi2_pportdata *ppd) +{ + /* + * Shut down the link and keep it down. First turn off that the + * driver wants to allow the link to be up (driver_link_ready). + * Then make sure the link is not automatically restarted + * (link_enabled). Cancel any pending restart. And finally + * go offline. + */ + ppd->driver_link_ready = 0; + ppd->link_enabled = 0; + + ppd->qsfp_retry_count = MAX_QSFP_RETRIES; /* prevent more retries */ + flush_delayed_work(&ppd->start_link_work); + cancel_delayed_work_sync(&ppd->start_link_work); + + ppd->offline_disabled_reason = + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_REBOOT); + hfi2_set_link_down_reason(ppd, OPA_LINKDOWN_REASON_REBOOT, 0, + OPA_LINKDOWN_REASON_REBOOT); + hfi2_set_link_state(ppd, HLS_DN_OFFLINE); + + /* disable the port */ + clear_rcvctrl(ppd, RCV_CTRL_RCV_PORT_ENABLE_SMASK); + flush_work(&ppd->dd->freeze_work); +} + +static inline int init_cpu_counters(struct hfi2_devdata *dd) +{ + struct hfi2_pportdata *ppd; + int i; + + ppd = (struct hfi2_pportdata *)(dd + 1); + for (i = 0; i < dd->num_pports; i++, ppd++) { + ppd->ibport_data.rvp.rc_acks = NULL; + ppd->ibport_data.rvp.rc_qacks = NULL; + ppd->ibport_data.rvp.rc_acks = alloc_percpu(u64); + ppd->ibport_data.rvp.rc_qacks = alloc_percpu(u64); + ppd->ibport_data.rvp.rc_delayed_comp = alloc_percpu(u64); + if (!ppd->ibport_data.rvp.rc_acks || + !ppd->ibport_data.rvp.rc_delayed_comp || + !ppd->ibport_data.rvp.rc_qacks) + return -ENOMEM; + } + + return 0; +} + +void hfi2_wfr_set_port_tid_config(struct hfi2_devdata *dd, int pidx, u16 ctxt, + u32 eager_base, u16 alloced, + u32 expected_base, u32 expected_count) +{ + u64 reg; + + /* set eager count and base index */ + reg = ((u64)(alloced >> RCV_SHIFT) << RCV_EGR_CTRL_EGR_CNT_SHIFT) | + ((eager_base >> RCV_SHIFT) << RCV_EGR_CTRL_EGR_BASE_INDEX_SHIFT); + write_rctxt_csr(dd, ctxt, dd->params->rcv_egr_ctrl_reg, reg); + + /* + * Set TID (expected) count and base index. + * rcd->expected_count is set to individual RcvArray entries, + * not pairs, and the CSR takes a pair-count in groups of + * four, so divide by 8. + */ + reg = ((u64)(expected_count >> RCV_SHIFT) + << RCV_TID_CTRL_TID_PAIR_CNT_SHIFT) | + ((expected_base >> RCV_SHIFT) + << RCV_TID_CTRL_TID_BASE_INDEX_SHIFT); + write_rctxt_csr(dd, ctxt, dd->params->rcv_tid_ctrl_reg, reg); + /* WFR does not have a port tid count */ +} + +/* RcvArray base address */ +static inline u8 __iomem *rcvarray_base(struct hfi2_devdata *dd, u32 ctxt) +{ + return dd->bar_maps[ctxt_bar_idx(ctxt)].rcvarray_wc; +} + +/* + * Update a TID entry of a given receive context. + * + * @rcd Receive context being updated. + * @index When type is PT_EAGER or PT_EXPECTED, index is the index into the + * receive array _relative_ to how the context is set up. Otherwise + * it is a raw index. + * @pa Physical DMA address. If invalidating, this should be zero. + * @order Order of map. If invalidating, this should be zero. + * @flush Forced flush. Otherwise, will flush on eager or on 32-byte boundary. + */ +void hfi2_wfr_put_tid(struct hfi2_ctxtdata *rcd, u32 index, u32 type, + unsigned long pa, u16 order, bool flush) +{ + struct hfi2_devdata *dd = rcd->dd; + u8 __iomem *base = rcvarray_base(dd, rcd->ctxt); + u64 reg; + + if (!(dd->flags & HFI2_PRESENT)) + return; + + if (type == PT_EAGER) + index += rcd->eager_base; + else if (type == PT_EXPECTED) + index += rcd->expected_base; + trace_hfi2_put_tid(dd, index, type, pa, order); + +#define RT_ADDR_SHIFT 12 /* 4KB kernel address boundary */ + reg = RCV_ARRAY_RT_WRITE_ENABLE_SMASK | + (u64)order << RCV_ARRAY_RT_BUF_SIZE_SHIFT | + ((pa >> RT_ADDR_SHIFT) & RCV_ARRAY_RT_ADDR_MASK) + << RCV_ARRAY_RT_ADDR_SHIFT; + trace_hfi2_write_rcvarray(base + (index * 8), reg); + writeq(reg, base + (index * 8)); + + if (type == PT_EAGER || flush || (index & 3) == 3) + flush_wc(); +} + +/* + * Write an "no-op" RcvArray entry. + * + * Called by the TID registration code to write to unused/unneeded RcvArray + * entries to fill out a write-combining buffer line. The HFI will ignore this + * write to the RcvArray entry. + */ +void hfi2_wfr_rcv_array_wc_fill(struct hfi2_ctxtdata *rcd, u32 index, u32 type) +{ + struct hfi2_devdata *dd = rcd->dd; + + /* + * Doing the WC fill writes only makes sense if the device is + * present and the RcvArray has been mapped as WC memory. + */ + if (dd->flags & HFI2_PRESENT) { + if (type == PT_EAGER) + index += rcd->eager_base; + else if (type == PT_EXPECTED) + index += rcd->expected_base; + + writeq(0, rcvarray_base(dd, rcd->ctxt) + (index * 8)); + if ((index & 3) == 3) + flush_wc(); + } +} + +static void wfr_init_tids(struct hfi2_devdata *dd) +{ + const u64 reg = RCV_ARRAY_RT_WRITE_ENABLE_SMASK; + u32 num_rcv; + u32 i; + + num_rcv = chip_rcv_array_count(dd); + for (i = 0; i < num_rcv; i++) { + /* WFR RcvArray addressing is not ctxt relative, just use 0 */ + writeq(reg, rcvarray_base(dd, 0) + (i * 8)); + if ((i & 3) == 3) + flush_wc(); + } +} + +void hfi2_clear_tids(struct hfi2_ctxtdata *rcd) +{ + u32 i; + + for (i = 0; i < rcd->egrbufs.alloced; i++) + rcd->dd->params->put_tid(rcd, i, PT_EAGER, 0, 0, false); + + for (i = 0; i < rcd->expected_count; i++) + rcd->dd->params->put_tid(rcd, i, PT_EXPECTED, 0, 0, false); +} + +static const char *const ib_cfg_name_strings[] = { + "HFI2_IB_CFG_LIDLMC", "HFI2_IB_CFG_LWID_DG_ENB", + "HFI2_IB_CFG_LWID_ENB", "HFI2_IB_CFG_LWID", + "HFI2_IB_CFG_SPD_ENB", "HFI2_IB_CFG_SPD", + "HFI2_IB_CFG_RXPOL_ENB", "HFI2_IB_CFG_LREV_ENB", + "HFI2_IB_CFG_LINKLATENCY", "HFI2_IB_CFG_HRTBT", + "HFI2_IB_CFG_OP_VLS", "HFI2_IB_CFG_VL_HIGH_CAP", + "HFI2_IB_CFG_VL_LOW_CAP", "HFI2_IB_CFG_OVERRUN_THRESH", + "HFI2_IB_CFG_PHYERR_THRESH", "HFI2_IB_CFG_LINKDEFAULT", + "HFI2_IB_CFG_PKEYS", "HFI2_IB_CFG_MTU", + "HFI2_IB_CFG_LSTATE", "HFI2_IB_CFG_VL_HIGH_LIMIT", + "HFI2_IB_CFG_PMA_TICKS", "HFI2_IB_CFG_PORT" +}; + +static const char *ib_cfg_name(int which) +{ + if (which < 0 || which >= ARRAY_SIZE(ib_cfg_name_strings)) + return "invalid"; + return ib_cfg_name_strings[which]; +} + +int hfi2_get_ib_cfg(struct hfi2_pportdata *ppd, int which) +{ + int val = 0; + + switch (which) { + case HFI2_IB_CFG_LWID_ENB: /* allowed Link-width */ + val = ppd->link_width_enabled; + break; + case HFI2_IB_CFG_LWID: /* currently active Link-width */ + val = ppd->link_width_active; + break; + case HFI2_IB_CFG_SPD_ENB: /* allowed Link speeds */ + val = ppd->link_speed_enabled; + break; + case HFI2_IB_CFG_SPD: /* current Link speed */ + val = ppd->link_speed_active; + break; + + case HFI2_IB_CFG_RXPOL_ENB: /* Auto-RX-polarity enable */ + case HFI2_IB_CFG_LREV_ENB: /* Auto-Lane-reversal enable */ + case HFI2_IB_CFG_LINKLATENCY: + goto unimplemented; + + case HFI2_IB_CFG_OP_VLS: + val = ppd->actual_vls_operational; + break; + case HFI2_IB_CFG_VL_HIGH_CAP: /* VL arb high priority table size */ + val = VL_ARB_HIGH_PRIO_TABLE_SIZE; + break; + case HFI2_IB_CFG_VL_LOW_CAP: /* VL arb low priority table size */ + val = VL_ARB_LOW_PRIO_TABLE_SIZE; + break; + case HFI2_IB_CFG_OVERRUN_THRESH: /* IB overrun threshold */ + val = ppd->overrun_threshold; + break; + case HFI2_IB_CFG_PHYERR_THRESH: /* IB PHY error threshold */ + val = ppd->phy_error_threshold; + break; + case HFI2_IB_CFG_LINKDEFAULT: /* IB link default (sleep/poll) */ + val = HLS_DEFAULT; + break; + + case HFI2_IB_CFG_HRTBT: /* Heartbeat off/enable/auto */ + case HFI2_IB_CFG_PMA_TICKS: + default: +unimplemented: + if (HFI2_CAP_IS_KSET(PRINT_UNIMPL)) + ppd_dev_info(ppd, "%s: which %s: not implemented\n", + __func__, ib_cfg_name(which)); + break; + } + + return val; +} + +/* + * The largest MAD packet size. + */ +#define MAX_MAD_PACKET 2048 + +/* + * Return the maximum header bytes that can go on the _wire_ + * for this device. This count includes the ICRC which is + * not part of the packet held in memory but it is appended + * by the HW. + * This is dependent on the device's receive header entry size. + * HFI allows this to be set per-receive context, but the + * driver presently enforces a global value. + */ +u32 hfi2_lrh_max_header_bytes(struct hfi2_pportdata *ppd) +{ + /* + * The maximum non-payload (MTU) bytes in LRH.PktLen are + * the Receive Header Entry Size minus the PBC (or RHF) size + * plus one DW for the ICRC appended by HW. + * + * hdrqentsize is in DW. + * + * Use this port's kernel contexts' receive header entry size. + */ + return (kctxt_hdrqentsize(ppd) - 2 /*PBC/RHF*/ + 1 /*ICRC*/) << 2; +} + +/* set what to accept in the port hardware */ +static void set_dlid_lmc(struct hfi2_pportdata *ppd, u32 mask, u32 lid) +{ + u64 c1 = hfi2_read_csr(ppd->dd, DCC_CFG_PORT_CONFIG1); + + c1 &= ~(DCC_CFG_PORT_CONFIG1_TARGET_DLID_SMASK | + DCC_CFG_PORT_CONFIG1_DLID_MASK_SMASK); + c1 |= ((lid & DCC_CFG_PORT_CONFIG1_TARGET_DLID_MASK) + << DCC_CFG_PORT_CONFIG1_TARGET_DLID_SHIFT) | + ((mask & DCC_CFG_PORT_CONFIG1_DLID_MASK_MASK) + << DCC_CFG_PORT_CONFIG1_DLID_MASK_SHIFT); + hfi2_write_csr(ppd->dd, DCC_CFG_PORT_CONFIG1, c1); +} + +/* set maximum MTU for the port */ +void hfi2_wfr_set_port_max_mtu(struct hfi2_pportdata *ppd, u32 maxvlmtu) +{ + u64 config; + u32 dcmtu; + + /* Adjust maximum MTU for the port in DC */ + dcmtu = maxvlmtu == 10240 ? DCC_CFG_PORT_MTU_CAP_10240 : + (ilog2(maxvlmtu >> 8) + 1); + config = hfi2_read_csr(ppd->dd, DCC_CFG_PORT_CONFIG); + config &= ~DCC_CFG_PORT_CONFIG_MTU_CAP_SMASK; + config |= ((u64)dcmtu & DCC_CFG_PORT_CONFIG_MTU_CAP_MASK) + << DCC_CFG_PORT_CONFIG_MTU_CAP_SHIFT; + hfi2_write_csr(ppd->dd, DCC_CFG_PORT_CONFIG, config); +} + +/* + * Set Send Length + * @ppd: per port data + * + * Set the MTU by limiting how many DWs may be sent. The SendLenCheck* + * registers compare against LRH.PktLen, so use the max bytes included + * in the LRH. + * + * This routine changes all VL values except VL15, which it maintains at + * the same value. + */ +static void set_send_length(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u32 maxvlmtu = ppd->vld[15].mtu; + int i, j; + u32 thres; + + /* per-vl send contexts are not present if port is not available */ + if (!port_available_ppd(ppd)) + return; + + for (i = 0; i < ppd->vls_supported; i++) { + if (ppd->vld[i].mtu > maxvlmtu) + maxvlmtu = ppd->vld[i].mtu; + } + + /* only WFR needs to write SendLenCheckn */ + if (dd->params->chip_type == CHIP_WFR) { + u32 max_hb = hfi2_lrh_max_header_bytes(ppd); + u64 len1 = 0; + u64 len2 = (((ppd->vld[15].mtu + max_hb) >> 2) & + SEND_LEN_CHECK1_LEN_VL15_MASK) + << SEND_LEN_CHECK1_LEN_VL15_SHIFT; + + for (i = 0; i < ppd->vls_supported; i++) { + if (i <= 3) + len1 |= (((ppd->vld[i].mtu + max_hb) >> 2) & + SEND_LEN_CHECK0_LEN_VL0_MASK) + << ((i % 4) * + SEND_LEN_CHECK0_LEN_VL1_SHIFT); + else + len2 |= (((ppd->vld[i].mtu + max_hb) >> 2) & + SEND_LEN_CHECK1_LEN_VL4_MASK) + << ((i % 4) * + SEND_LEN_CHECK1_LEN_VL5_SHIFT); + } + write_eport_csr(dd, ppd->hw_pidx, + dd->params->send_len_check0_reg, len1); + write_eport_csr(dd, ppd->hw_pidx, + dd->params->send_len_check1_reg, len2); + } + /* adjust kernel credit return thresholds based on new MTUs */ + /* all kernel receive contexts have the same hdrqentsize */ + for (i = 0; i < ppd->vls_supported; i++) { + thres = min(hfi2_sc_percent_to_threshold(ppd->vld[i].sc, 50), + hfi2_sc_mtu_to_threshold(ppd->vld[i].sc, + ppd->vld[i].mtu, + kctxt_hdrqentsize(ppd))); + for (j = 0; j < INIT_SC_PER_VL; j++) + hfi2_sc_set_cr_threshold( + hfi2_pio_select_send_context_vl(ppd, j, i), + thres); + } + thres = min(hfi2_sc_percent_to_threshold(ppd->vld[15].sc, 50), + hfi2_sc_mtu_to_threshold(ppd->vld[15].sc, ppd->vld[15].mtu, + kctxt_hdrqentsize(ppd))); + hfi2_sc_set_cr_threshold(ppd->vld[15].sc, thres); + + dd->params->set_port_max_mtu(ppd, maxvlmtu); +} + +static void set_lidlmc(struct hfi2_pportdata *ppd) +{ + int i; + u64 sreg = 0; + struct hfi2_devdata *dd = ppd->dd; + u32 mask = ~((1U << ppd->lmc) - 1); + u32 lid; + + /* + * Program 0 in CSR if port lid is extended. This prevents + * 9B packets being sent out for large lids. + */ + lid = (ppd->lid >= be16_to_cpu(IB_MULTICAST_LID_BASE)) ? 0 : ppd->lid; + + /* set port hardware */ + set_dlid_lmc(ppd, mask, lid); + + /* + * Iterate over all the send contexts for this port and set their SLID + * check. + */ + if (dd->params->chip_type == CHIP_WFR) { + sreg = ((mask & SEND_CTXT_CHECK_SLID_MASK_MASK) + << SEND_CTXT_CHECK_SLID_MASK_SHIFT) | + (((lid & mask) & SEND_CTXT_CHECK_SLID_VALUE_MASK) + << SEND_CTXT_CHECK_SLID_VALUE_SHIFT); + } else { + sreg = ((u64)(mask & 0xffffff) << 32) | (lid & 0xffffff) | + BIT_ULL(63); /* PermissiveSlidAllowed */ + } + + for (i = 0; i < dd->num_send_contexts; i++) { + struct send_context *sc = dd->send_contexts[i].sc; + + if (!sc) + continue; + if (sc->ppd != ppd) + continue; + + hfi2_cdbg(LINKVERB, "SendContext[%d].SLID_CHECK = 0x%x", i, + (u32)sreg); + hfi2_priv_reg_op(dd, ppd->hw_pidx, sc->hw_context, sc->type, + SC_CHK_SLID_OP, sreg); + } + + /* Now we have to do the same thing for the sdma engines */ + hfi2_sdma_update_lmc(dd, mask, lid); +} + +static const char *state_completed_string(u32 completed) +{ + static const char *const state_completed[] = { "EstablishComm", + "OptimizeEQ", + "VerifyCap" }; + + if (completed < ARRAY_SIZE(state_completed)) + return state_completed[completed]; + + return "unknown"; +} + +static const char all_lanes_dead_timeout_expired[] = + "All lanes were inactive – was the interconnect media removed?"; +static const char tx_out_of_policy[] = + "Passing lanes on local port do not meet the local link width policy"; +static const char no_state_complete[] = + "State timeout occurred before link partner completed the state"; +static const char *const state_complete_reasons[] = { + [0x00] = "Reason unknown", + [0x01] = "Link was halted by driver, refer to LinkDownReason", + [0x02] = "Link partner reported failure", + [0x10] = "Unable to achieve frame sync on any lane", + [0x11] = "Unable to find a common bit rate with the link partner", + [0x12] = + "Unable to achieve frame sync on sufficient lanes to meet the local link width policy", + [0x13] = + "Unable to identify preset equalization on sufficient lanes to meet the local link width policy", + [0x14] = no_state_complete, + [0x15] = + "State timeout occurred before link partner identified equalization presets", + [0x16] = + "Link partner completed the EstablishComm state, but the passing lanes do not meet the local link width policy", + [0x17] = tx_out_of_policy, + [0x20] = all_lanes_dead_timeout_expired, + [0x21] = + "Unable to achieve acceptable BER on sufficient lanes to meet the local link width policy", + [0x22] = no_state_complete, + [0x23] = + "Link partner completed the OptimizeEq state, but the passing lanes do not meet the local link width policy", + [0x24] = tx_out_of_policy, + [0x30] = all_lanes_dead_timeout_expired, + [0x31] = + "State timeout occurred waiting for host to process received frames", + [0x32] = no_state_complete, + [0x33] = + "Link partner completed the VerifyCap state, but the passing lanes do not meet the local link width policy", + [0x34] = tx_out_of_policy, + [0x35] = "Negotiated link width is mutually exclusive", + [0x36] = + "Timed out before receiving verifycap frames in VerifyCap.Exchange", + [0x37] = "Unable to resolve secure data exchange", +}; + +static const char *state_complete_reason_code_string(struct hfi2_pportdata *ppd, + u32 code) +{ + const char *str = NULL; + + if (code < ARRAY_SIZE(state_complete_reasons)) + str = state_complete_reasons[code]; + + if (str) + return str; + return "Reserved"; +} + +/* describe the given last state complete frame */ +static void decode_state_complete(struct hfi2_pportdata *ppd, u32 frame, + const char *prefix) +{ + u32 success; + u32 state; + u32 reason; + u32 lanes; + + /* + * Decode frame: + * [ 0: 0] - success + * [ 3: 1] - state + * [ 7: 4] - next state timeout + * [15: 8] - reason code + * [31:16] - lanes + */ + success = frame & 0x1; + state = (frame >> 1) & 0x7; + reason = (frame >> 8) & 0xff; + lanes = (frame >> 16) & 0xffff; + + ppd_dev_err(ppd, "Last %s LNI state complete frame 0x%08x:\n", prefix, + frame); + ppd_dev_err(ppd, " last reported state state: %s (0x%x)\n", + state_completed_string(state), state); + ppd_dev_err(ppd, " state successfully completed: %s\n", + success ? "yes" : "no"); + ppd_dev_err(ppd, " fail reason 0x%x: %s\n", reason, + state_complete_reason_code_string(ppd, reason)); + ppd_dev_err(ppd, " passing lane mask: 0x%x", lanes); +} + +/* + * Read the last state complete frames and explain them. This routine + * expects to be called if the link went down during link negotiation + * and initialization (LNI). That is, anywhere between polling and link up. + */ +static void check_lni_states(struct hfi2_pportdata *ppd) +{ + u32 last_local_state; + u32 last_remote_state; + + read_last_local_state(ppd->dd, &last_local_state); + read_last_remote_state(ppd->dd, &last_remote_state); + + /* + * Don't report anything if there is nothing to report. A value of + * 0 means the link was taken down while polling and there was no + * training in-process. + */ + if (last_local_state == 0 && last_remote_state == 0) + return; + + decode_state_complete(ppd, last_local_state, "transmitted"); + decode_state_complete(ppd, last_remote_state, "received"); +} + +/* wait for wait_ms for LINK_TRANSFER_ACTIVE to go to 1 */ +static int wait_link_transfer_active(struct hfi2_devdata *dd, int wait_ms) +{ + u64 reg; + unsigned long timeout; + + /* watch LCB_STS_LINK_TRANSFER_ACTIVE */ + timeout = jiffies + msecs_to_jiffies(wait_ms); + while (1) { + reg = hfi2_read_csr(dd, DC_LCB_STS_LINK_TRANSFER_ACTIVE); + if (reg) + break; + if (time_after(jiffies, timeout)) { + dd_dev_err( + dd, + "timeout waiting for LINK_TRANSFER_ACTIVE\n"); + return -ETIMEDOUT; + } + udelay(2); + } + return 0; +} + +/* called when the logical link state is not down as it should be */ +static void force_logical_link_state_down(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + + /* + * Bring link up in LCB hfi2_loopback + */ + hfi2_write_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET, 1); + hfi2_write_csr(dd, DC_LCB_CFG_IGNORE_LOST_RCLK, + DC_LCB_CFG_IGNORE_LOST_RCLK_EN_SMASK); + + hfi2_write_csr(dd, DC_LCB_CFG_LANE_WIDTH, 0); + hfi2_write_csr(dd, DC_LCB_CFG_REINIT_AS_SLAVE, 0); + hfi2_write_csr(dd, DC_LCB_CFG_CNT_FOR_SKIP_STALL, 0x110); + hfi2_write_csr(dd, DC_LCB_CFG_LOOPBACK, 0x2); + + hfi2_write_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET, 0); + (void)hfi2_read_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET); + udelay(3); + hfi2_write_csr(dd, DC_LCB_CFG_ALLOW_LINK_UP, 1); + hfi2_write_csr(dd, DC_LCB_CFG_RUN, 1ull << DC_LCB_CFG_RUN_EN_SHIFT); + + wait_link_transfer_active(dd, 100); + + /* + * Bring the link down again. + */ + hfi2_write_csr(dd, DC_LCB_CFG_TX_FIFOS_RESET, 1); + hfi2_write_csr(dd, DC_LCB_CFG_ALLOW_LINK_UP, 0); + hfi2_write_csr(dd, DC_LCB_CFG_IGNORE_LOST_RCLK, 0); + + ppd_dev_info(ppd, "logical state forced to LINK_DOWN\n"); +} + +/* + * Helper for hfi2_set_link_state(). Do not call except from that routine. + * Expects ppd->hls_mutex to be held. + * + * @rem_reason value to be sent to the neighbor + * + * LinkDownReasons only set if transition succeeds. + */ +static int goto_offline(struct hfi2_pportdata *ppd, u8 rem_reason) +{ + struct hfi2_devdata *dd = ppd->dd; + u32 previous_state; + int offline_state_ret; + int ret; + + update_lcb_cache(ppd); + + previous_state = ppd->host_link_state; + ppd->host_link_state = HLS_GOING_OFFLINE; + + /* start offline transition */ + ret = set_physical_link_state(dd, (rem_reason << 8) | PLS_OFFLINE); + + if (ret != HCMD_SUCCESS) { + ppd_dev_err( + ppd, + "Failed to transition to Offline link state, return %d\n", + ret); + return -EINVAL; + } + if (ppd->offline_disabled_reason == + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_NONE)) + ppd->offline_disabled_reason = + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_TRANSIENT); + + offline_state_ret = wait_phys_link_offline_substates(ppd, 10000); + if (offline_state_ret < 0) + return offline_state_ret; + + /* Disabling AOC transmitters */ + if (ppd->port_type == PORT_TYPE_QSFP && + ppd->qsfp_info.limiting_active && hfi2_qsfp_mod_present(ppd)) { + int ret; + + ret = hfi2_acquire_chip_resource(dd, qsfp_resource(dd), + QSFP_WAIT); + if (ret == 0) { + hfi2_set_qsfp_tx(ppd, 0); + hfi2_release_chip_resource(dd, qsfp_resource(dd)); + } else { + /* not fatal, but should warn */ + ppd_dev_err( + ppd, + "Unable to acquire lock to turn off QSFP TX\n"); + } + } + + /* + * Wait for the offline.Quiet transition if it hasn't happened yet. It + * can take a while for the link to go down. + */ + if (offline_state_ret != PLS_OFFLINE_QUIET) { + ret = wait_physical_linkstate(ppd, PLS_OFFLINE, 30000); + if (ret < 0) + return ret; + } + + /* + * Now in charge of LCB - must be after the physical state is + * offline.quiet and before host_link_state is changed. + */ + set_host_lcb_access(dd); + hfi2_write_csr(dd, DC_LCB_ERR_EN, ~0ull); /* watch LCB errors */ + + /* make sure the logical state is also down */ + ret = wait_logical_linkstate(ppd, IB_PORT_DOWN, 1000); + if (ret) + force_logical_link_state_down(ppd); + + ppd->host_link_state = HLS_LINK_COOLDOWN; /* LCB access allowed */ + hfi2_update_statusp(ppd, IB_PORT_DOWN); + + /* + * The LNI has a mandatory wait time after the physical state + * moves to Offline.Quiet. The wait time may be different + * depending on how the link went down. The 8051 firmware + * will observe the needed wait time and only move to ready + * when that is completed. The largest of the quiet timeouts + * is 6s, so wait that long and then at least 0.5s more for + * other transitions, and another 0.5s for a buffer. + */ + ret = hfi2_wait_fm_ready(dd, 7000); + if (ret) { + ppd_dev_err( + ppd, + "After going offline, timed out waiting for the 8051 to become ready to accept host requests\n"); + /* state is really offline, so make it so */ + ppd->host_link_state = HLS_DN_OFFLINE; + return ret; + } + + /* + * The state is now offline and the 8051 is ready to accept host + * requests. + * - change our state + * - notify others if we were previously in a linkup state + */ + ppd->host_link_state = HLS_DN_OFFLINE; + if (previous_state & HLS_UP) { + /* went down while link was up */ + hfi2_handle_linkup_change(ppd, 0); + } else if (previous_state & + (HLS_DN_POLL | HLS_VERIFY_CAP | HLS_GOING_UP)) { + /* went down while attempting link up */ + check_lni_states(ppd); + + /* The QSFP doesn't need to be reset on LNI failure */ + ppd->qsfp_info.reset_needed = 0; + } + + /* the active link width (downgrade) is 0 on link down */ + ppd->link_width_active = 0; + ppd->link_width_downgrade_tx_active = 0; + ppd->link_width_downgrade_rx_active = 0; + ppd->current_egress_rate = 0; + return 0; +} + +/* return the link state name */ +const char *hfi2_link_state_name(u32 state) +{ + const char *name; + int n = ilog2(state); + static const char *const names[] = { + [__HLS_UP_INIT_BP] = "INIT", + [__HLS_UP_ARMED_BP] = "ARMED", + [__HLS_UP_ACTIVE_BP] = "ACTIVE", + [__HLS_DN_DOWNDEF_BP] = "DOWNDEF", + [__HLS_DN_POLL_BP] = "POLL", + [__HLS_DN_DISABLE_BP] = "DISABLE", + [__HLS_DN_OFFLINE_BP] = "OFFLINE", + [__HLS_VERIFY_CAP_BP] = "VERIFY_CAP", + [__HLS_GOING_UP_BP] = "GOING_UP", + [__HLS_GOING_OFFLINE_BP] = "GOING_OFFLINE", + [__HLS_LINK_COOLDOWN_BP] = "LINK_COOLDOWN" + }; + + name = n < ARRAY_SIZE(names) ? names[n] : NULL; + return name ? name : "unknown"; +} + +/* return the link state reason name */ +const char *hfi2_link_state_reason_name(struct hfi2_pportdata *ppd, u32 state) +{ + if (state == HLS_UP_INIT) { + switch (ppd->linkinit_reason) { + case OPA_LINKINIT_REASON_LINKUP: + return "(LINKUP)"; + case OPA_LINKINIT_REASON_FLAPPING: + return "(FLAPPING)"; + case OPA_LINKINIT_OUTSIDE_POLICY: + return "(OUTSIDE_POLICY)"; + case OPA_LINKINIT_QUARANTINED: + return "(QUARANTINED)"; + case OPA_LINKINIT_INSUFIC_CAPABILITY: + return "(INSUFIC_CAPABILITY)"; + default: + break; + } + } + return ""; +} + +/* + * hfi2_driver_pstate - convert the driver's notion of a port's + * state (an HLS_*) into a physical state (a {IB,OPA}_PORTPHYSSTATE_*). + * Return -1 (converted to a u32) to indicate error. + */ +u32 hfi2_driver_pstate(struct hfi2_pportdata *ppd) +{ + switch (ppd->host_link_state) { + case HLS_UP_INIT: + case HLS_UP_ARMED: + case HLS_UP_ACTIVE: + return IB_PORTPHYSSTATE_LINKUP; + case HLS_DN_POLL: + return IB_PORTPHYSSTATE_POLLING; + case HLS_DN_DISABLE: + return IB_PORTPHYSSTATE_DISABLED; + case HLS_DN_OFFLINE: + return OPA_PORTPHYSSTATE_OFFLINE; + case HLS_VERIFY_CAP: + return IB_PORTPHYSSTATE_TRAINING; + case HLS_GOING_UP: + return IB_PORTPHYSSTATE_TRAINING; + case HLS_GOING_OFFLINE: + return OPA_PORTPHYSSTATE_OFFLINE; + case HLS_LINK_COOLDOWN: + return OPA_PORTPHYSSTATE_OFFLINE; + case HLS_DN_DOWNDEF: + default: + ppd_dev_err(ppd, "invalid host_link_state 0x%x\n", + ppd->host_link_state); + return -1; + } +} + +/* + * hfi2_driver_lstate - convert the driver's notion of a port's + * state (an HLS_*) into a logical state (a IB_PORT_*). Return -1 + * (converted to a u32) to indicate error. + */ +u32 hfi2_driver_lstate(struct hfi2_pportdata *ppd) +{ + if (ppd->host_link_state && (ppd->host_link_state & HLS_DOWN)) + return IB_PORT_DOWN; + + switch (ppd->host_link_state & HLS_UP) { + case HLS_UP_INIT: + return IB_PORT_INIT; + case HLS_UP_ARMED: + return IB_PORT_ARMED; + case HLS_UP_ACTIVE: + return IB_PORT_ACTIVE; + default: + ppd_dev_err(ppd, "invalid host_link_state 0x%x\n", + ppd->host_link_state); + return -1; + } +} + +void hfi2_set_link_down_reason(struct hfi2_pportdata *ppd, u8 lcl_reason, + u8 neigh_reason, u8 rem_reason) +{ + if (ppd->local_link_down_reason.latest == 0 && + ppd->neigh_link_down_reason.latest == 0) { + ppd->local_link_down_reason.latest = lcl_reason; + ppd->neigh_link_down_reason.latest = neigh_reason; + ppd->remote_link_down_reason = rem_reason; + } +} + +/** + * data_vls_operational() - Verify if data VL BCT credits and MTU + * are both set. + * @ppd: pointer to hfi2_pportdata structure + * + * Return: true - Ok, false -otherwise. + */ +static inline bool data_vls_operational(struct hfi2_pportdata *ppd) +{ + int i; + u64 reg; + + if (!ppd->actual_vls_operational) + return false; + + for (i = 0; i < ppd->vls_supported; i++) { + u32 off = ppd->dd->params->send_cm_credit_vl_reg + (8 * i); + + reg = read_eport_csr(ppd->dd, ppd->hw_pidx, off); + if ((reg && !ppd->vld[i].mtu) || (!reg && ppd->vld[i].mtu)) + return false; + } + + return true; +} + +/* + * Change the physical and/or logical link state. + * + * Do not call this routine while inside an interrupt. It contains + * calls to routines that can take multiple seconds to finish. + * + * Returns 0 on success, -errno on failure. + */ +int hfi2_set_link_state(struct hfi2_pportdata *ppd, u32 state) +{ + struct hfi2_devdata *dd = ppd->dd; + int ret1, ret = 0; + int orig_new_state, poll_bounce; + + mutex_lock(&ppd->hls_lock); + + orig_new_state = state; + if (state == HLS_DN_DOWNDEF) + state = HLS_DEFAULT; + + /* interpret poll -> poll as a link bounce */ + poll_bounce = ppd->host_link_state == HLS_DN_POLL && + state == HLS_DN_POLL; + + ppd_dev_info(ppd, "%s: current %s, new %s %s%s\n", __func__, + hfi2_link_state_name(ppd->host_link_state), + hfi2_link_state_name(orig_new_state), + poll_bounce ? "(bounce) " : "", + hfi2_link_state_reason_name(ppd, state)); + + /* + * If we're going to a (HLS_*) link state that implies the logical + * link state is neither of (IB_PORT_ARMED, IB_PORT_ACTIVE), then + * reset is_sm_config_started to 0. + */ + if (!(state & (HLS_UP_ARMED | HLS_UP_ACTIVE))) + ppd->is_sm_config_started = 0; + + /* + * Do nothing if the states match. Let a poll to poll link bounce + * go through. + */ + if (ppd->host_link_state == state && !poll_bounce) + goto done; + + switch (state) { + case HLS_UP_INIT: + if (ppd->host_link_state == HLS_DN_POLL && hfi2_quick_linkup) { + /* + * Quick link up jumps from polling to here. + * Accept that here. + */ + /* OK */ + } else if (ppd->host_link_state != HLS_GOING_UP) { + goto unexpected; + } + + /* + * Wait for Link_Up physical state. + * Physical and Logical states should already + * be transitioned to LinkUp and LinkInit respectively. + */ + ret = wait_physical_linkstate(ppd, PLS_LINKUP, 1000); + if (ret) { + dd_dev_err( + dd, + "%s: physical state did not change to LINK-UP\n", + __func__); + break; + } + + ret = wait_logical_linkstate(ppd, IB_PORT_INIT, 1000); + if (ret) { + ppd_dev_err( + ppd, + "%s: logical state did not change to INIT\n", + __func__); + break; + } + + /* clear old transient LINKINIT_REASON code */ + if (ppd->linkinit_reason >= OPA_LINKINIT_REASON_CLEAR) + ppd->linkinit_reason = OPA_LINKINIT_REASON_LINKUP; + + /* enable the port */ + add_rcvctrl(ppd, RCV_CTRL_RCV_PORT_ENABLE_SMASK); + + hfi2_handle_linkup_change(ppd, 1); + hfi2_pio_kernel_linkup(ppd); + /* tell engines to go running after a link bounce */ + hfi2_sdma_all_running(dd); + + /* + * After link up, a new link width will have been set. + * Update the xmit counters with regards to the new + * link width. + */ + hfi2_update_xmit_counters(ppd, ppd->link_width_active); + + ppd->host_link_state = HLS_UP_INIT; + hfi2_update_statusp(ppd, IB_PORT_INIT); + break; + case HLS_UP_ARMED: + if (ppd->host_link_state != HLS_UP_INIT) + goto unexpected; + + if (!data_vls_operational(ppd)) { + ppd_dev_err(ppd, "%s: Invalid data VL credits or mtu\n", + __func__); + ret = -EINVAL; + break; + } + + set_logical_state(dd, LSTATE_ARMED); + ret = wait_logical_linkstate(ppd, IB_PORT_ARMED, 1000); + if (ret) { + ppd_dev_err( + ppd, + "%s: logical state did not change to ARMED\n", + __func__); + break; + } + ppd->host_link_state = HLS_UP_ARMED; + hfi2_update_statusp(ppd, IB_PORT_ARMED); + break; + case HLS_UP_ACTIVE: + if (ppd->host_link_state != HLS_UP_ARMED) + goto unexpected; + + set_logical_state(dd, LSTATE_ACTIVE); + ret = wait_logical_linkstate(ppd, IB_PORT_ACTIVE, 1000); + if (ret) { + ppd_dev_err( + ppd, + "%s: logical state did not change to ACTIVE\n", + __func__); + } else { + ppd->host_link_state = HLS_UP_ACTIVE; + hfi2_update_statusp(ppd, IB_PORT_ACTIVE); + hfi2_go_port_active(ppd); + } + break; + case HLS_DN_POLL: + if ((ppd->host_link_state == HLS_DN_DISABLE || + ppd->host_link_state == HLS_DN_OFFLINE) && + dd->dc_shutdown) + dc_start(dd); + /* Hand LED control to the DC */ + hfi2_write_csr(dd, DCC_CFG_LED_CNTRL, 0); + + if (ppd->host_link_state != HLS_DN_OFFLINE) { + u8 tmp = ppd->link_enabled; + + ret = goto_offline(ppd, ppd->remote_link_down_reason); + if (ret) { + ppd->link_enabled = tmp; + break; + } + ppd->remote_link_down_reason = 0; + + if (ppd->driver_link_ready) + ppd->link_enabled = 1; + } + + hfi2_set_all_slowpath(ppd); + ret = set_local_link_attributes(ppd); + if (ret) + break; + + ppd->port_error_action = 0; + + if (hfi2_quick_linkup) { + /* quick linkup does not go into polling */ + ret = do_quick_linkup(dd); + } else { + ret1 = set_physical_link_state(dd, PLS_POLLING); + if (!ret1) + ret1 = wait_phys_link_out_of_offline(ppd, 3000); + if (ret1 != HCMD_SUCCESS) { + ppd_dev_err( + ppd, + "Failed to transition to Polling link state, return 0x%x\n", + ret1); + ret = -EINVAL; + } + } + + /* + * Change the host link state after requesting DC8051 to + * change its physical state so that we can ignore any + * interrupt with stale LNI(XX) error, which will not be + * cleared until DC8051 transitions to Polling state. + */ + ppd->host_link_state = HLS_DN_POLL; + ppd->offline_disabled_reason = + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_NONE); + /* + * If an error occurred above, go back to offline. The + * caller may reschedule another attempt. + */ + if (ret) + goto_offline(ppd, 0); + else + log_physical_state(ppd, PLS_POLLING); + break; + case HLS_DN_DISABLE: + /* link is disabled */ + ppd->link_enabled = 0; + + /* allow any state to transition to disabled */ + + /* must transition to offline first */ + if (ppd->host_link_state != HLS_DN_OFFLINE) { + ret = goto_offline(ppd, ppd->remote_link_down_reason); + if (ret) + break; + ppd->remote_link_down_reason = 0; + } + + if (!dd->dc_shutdown) { + ret1 = set_physical_link_state(dd, PLS_DISABLED); + if (ret1 != HCMD_SUCCESS) { + ppd_dev_err( + ppd, + "Failed to transition to Disabled link state, return 0x%x\n", + ret1); + ret = -EINVAL; + break; + } + ret = wait_physical_linkstate(ppd, PLS_DISABLED, 10000); + if (ret) { + ppd_dev_err( + ppd, + "%s: physical state did not change to DISABLED\n", + __func__); + break; + } + dc_shutdown(dd); + } + ppd->host_link_state = HLS_DN_DISABLE; + break; + case HLS_DN_OFFLINE: + if (ppd->host_link_state == HLS_DN_DISABLE) + dc_start(dd); + + /* allow any state to transition to offline */ + ret = goto_offline(ppd, ppd->remote_link_down_reason); + if (!ret) + ppd->remote_link_down_reason = 0; + break; + case HLS_VERIFY_CAP: + if (ppd->host_link_state != HLS_DN_POLL) + goto unexpected; + ppd->host_link_state = HLS_VERIFY_CAP; + log_physical_state(ppd, PLS_CONFIGPHY_VERIFYCAP); + break; + case HLS_GOING_UP: + if (ppd->host_link_state != HLS_VERIFY_CAP) + goto unexpected; + + ret1 = set_physical_link_state(dd, PLS_LINKUP); + if (ret1 != HCMD_SUCCESS) { + ppd_dev_err( + ppd, + "Failed to transition to link up state, return 0x%x\n", + ret1); + ret = -EINVAL; + break; + } + ppd->host_link_state = HLS_GOING_UP; + break; + + case HLS_GOING_OFFLINE: /* transient within goto_offline() */ + case HLS_LINK_COOLDOWN: /* transient within goto_offline() */ + default: + ppd_dev_info(ppd, "%s: state 0x%x: not supported\n", __func__, + state); + ret = -EINVAL; + break; + } + + goto done; + +unexpected: + ppd_dev_err(ppd, "%s: unexpected state transition from %s to %s\n", + __func__, hfi2_link_state_name(ppd->host_link_state), + hfi2_link_state_name(state)); + ret = -EINVAL; + +done: + mutex_unlock(&ppd->hls_lock); + + return ret; +} + +int hfi2_set_ib_cfg(struct hfi2_pportdata *ppd, int which, u32 val) +{ + u64 reg; + int ret = 0; + + switch (which) { + case HFI2_IB_CFG_LIDLMC: + set_lidlmc(ppd); + break; + case HFI2_IB_CFG_VL_HIGH_LIMIT: + /* + * The VL Arbitrator high limit is sent in units of 4k + * bytes, while HFI stores it in units of 64 bytes. + */ + val *= 4096 / 64; + reg = ((u64)val & SEND_HIGH_PRIORITY_LIMIT_LIMIT_MASK) + << SEND_HIGH_PRIORITY_LIMIT_LIMIT_SHIFT; + write_eport_csr(ppd->dd, ppd->hw_pidx, + ppd->dd->params->send_high_priority_limit_reg, + reg); + break; + case HFI2_IB_CFG_LINKDEFAULT: /* IB link default (sleep/poll) */ + /* HFI only supports POLL as the default link down state */ + if (val != HLS_DN_POLL) + ret = -EINVAL; + break; + case HFI2_IB_CFG_OP_VLS: + if (ppd->vls_operational != val) { + ppd->vls_operational = val; + if (!ppd->port) + ret = -EINVAL; + } + break; + /* + * For link width, link width downgrade, and speed enable, always AND + * the setting with what is actually supported. This has two benefits. + * First, enabled can't have unsupported values, no matter what the + * SM or FM might want. Second, the ALL_SUPPORTED wildcards that mean + * "fill in with your supported value" have all the bits in the + * field set, so simply ANDing with supported has the desired result. + */ + case HFI2_IB_CFG_LWID_ENB: /* set allowed Link-width */ + ppd->link_width_enabled = val & ppd->link_width_supported; + break; + case HFI2_IB_CFG_LWID_DG_ENB: /* set allowed link width downgrade */ + ppd->link_width_downgrade_enabled = + val & ppd->link_width_downgrade_supported; + break; + case HFI2_IB_CFG_SPD_ENB: /* allowed Link speeds */ + ppd->link_speed_enabled = val & ppd->link_speed_supported; + break; + case HFI2_IB_CFG_OVERRUN_THRESH: /* IB overrun threshold */ + /* + * HFI does not follow IB specs, save this value + * so we can report it, if asked. + */ + ppd->overrun_threshold = val; + break; + case HFI2_IB_CFG_PHYERR_THRESH: /* IB PHY error threshold */ + /* + * HFI does not follow IB specs, save this value + * so we can report it, if asked. + */ + ppd->phy_error_threshold = val; + break; + + case HFI2_IB_CFG_MTU: + set_send_length(ppd); + break; + + case HFI2_IB_CFG_PKEYS: + if (HFI2_CAP_IS_KSET(PKEY_CHECK)) + set_partition_keys(ppd); + break; + + default: + if (HFI2_CAP_IS_KSET(PRINT_UNIMPL)) + ppd_dev_info( + ppd, + "%s: which %s, val 0x%x: not implemented\n", + __func__, ib_cfg_name(which), val); + break; + } + return ret; +} + +/* begin functions related to vl arbitration table caching */ +static void init_vl_arb_caches(struct hfi2_pportdata *ppd) +{ + int i; + + BUILD_BUG_ON(VL_ARB_TABLE_SIZE != VL_ARB_LOW_PRIO_TABLE_SIZE); + BUILD_BUG_ON(VL_ARB_TABLE_SIZE != VL_ARB_HIGH_PRIO_TABLE_SIZE); + + /* + * Note that we always return values directly from the + * 'vl_arb_cache' (and do no CSR reads) in response to a + * 'Get(VLArbTable)'. This is obviously correct after a + * 'Set(VLArbTable)', since the cache will then be up to + * date. But it's also correct prior to any 'Set(VLArbTable)' + * since then both the cache, and the relevant h/w registers + * will be zeroed. + */ + + for (i = 0; i < MAX_PRIO_TABLE; i++) + spin_lock_init(&ppd->vl_arb_cache[i].lock); +} + +/* + * vl_arb_lock_cache + * + * All other vl_arb_* functions should be called only after locking + * the cache. + */ +static inline struct vl_arb_cache *vl_arb_lock_cache(struct hfi2_pportdata *ppd, + int idx) +{ + if (idx != LO_PRIO_TABLE && idx != HI_PRIO_TABLE) + return NULL; + spin_lock(&ppd->vl_arb_cache[idx].lock); + return &ppd->vl_arb_cache[idx]; +} + +static inline void vl_arb_unlock_cache(struct hfi2_pportdata *ppd, int idx) +{ + spin_unlock(&ppd->vl_arb_cache[idx].lock); +} + +static void vl_arb_get_cache(struct vl_arb_cache *cache, + struct ib_vl_weight_elem *vl) +{ + memcpy(vl, cache->table, VL_ARB_TABLE_SIZE * sizeof(*vl)); +} + +static void vl_arb_set_cache(struct vl_arb_cache *cache, + struct ib_vl_weight_elem *vl) +{ + memcpy(cache->table, vl, VL_ARB_TABLE_SIZE * sizeof(*vl)); +} + +static int vl_arb_match_cache(struct vl_arb_cache *cache, + struct ib_vl_weight_elem *vl) +{ + return !memcmp(cache->table, vl, VL_ARB_TABLE_SIZE * sizeof(*vl)); +} + +/* end functions related to vl arbitration table caching */ + +static int set_vl_weights(struct hfi2_pportdata *ppd, u32 target, u32 size, + struct ib_vl_weight_elem *vl) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + unsigned int i, is_up = 0; + int drain, ret = 0; + + /* only set the registers on WFR */ + if (dd->params->chip_type != CHIP_WFR) + return 0; + + mutex_lock(&ppd->hls_lock); + + if (ppd->host_link_state & HLS_UP) + is_up = 1; + + drain = !hfi2_is_ax(dd) && is_up; + + if (drain) + /* + * Before adjusting VL arbitration weights, empty per-VL + * FIFOs, otherwise a packet whose VL weight is being + * set to 0 could get stuck in a FIFO with no chance to + * egress. + */ + ret = hfi2_stop_drain_data_vls(ppd); + + if (ret) { + ppd_dev_err( + ppd, + "%s: cannot stop/drain VLs - refusing to change VL arbitration weights\n", + __func__); + goto err; + } + + for (i = 0; i < size; i++, vl++) { + /* + * NOTE: The low priority shift and mask are used here, but + * they are the same for both the low and high registers. + */ + reg = (((u64)vl->vl & SEND_LOW_PRIORITY_LIST_VL_MASK) + << SEND_LOW_PRIORITY_LIST_VL_SHIFT) | + (((u64)vl->weight & SEND_LOW_PRIORITY_LIST_WEIGHT_MASK) + << SEND_LOW_PRIORITY_LIST_WEIGHT_SHIFT); + write_eport_csr(dd, ppd->hw_pidx, target + (i * 8), reg); + } + hfi2_pio_send_control(ppd, PSC_GLOBAL_VLARB_ENABLE); + + if (drain) + hfi2_open_fill_data_vls(ppd); /* reopen all VLs */ + +err: + mutex_unlock(&ppd->hls_lock); + + return ret; +} + +/* + * Read one credit merge VL register. + */ +static void read_one_cm_vl(struct hfi2_pportdata *ppd, u32 csr, + struct vl_limit *vll) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg = read_eport_csr(dd, ppd->hw_pidx, csr); + + vll->dedicated = cpu_to_be16( + (reg >> SEND_CM_CREDIT_VL_DEDICATED_LIMIT_VL_SHIFT) & + SEND_CM_CREDIT_VL_DEDICATED_LIMIT_VL_MASK); + vll->shared = + cpu_to_be16((reg >> SEND_CM_CREDIT_VL_SHARED_LIMIT_VL_SHIFT) & + SEND_CM_CREDIT_VL_SHARED_LIMIT_VL_MASK); +} + +/* + * Read the current credit merge limits. + */ +static int get_buffer_control(struct hfi2_pportdata *ppd, + struct buffer_control *bc, u16 *overall_limit) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + int i; + + /* not all entries are filled in */ + memset(bc, 0, sizeof(*bc)); + + /* OPA and HFI have a 1-1 mapping */ + for (i = 0; i < TXE_NUM_DATA_VL; i++) + read_one_cm_vl(ppd, dd->params->send_cm_credit_vl_reg + (8 * i), + &bc->vl[i]); + + /* NOTE: assumes that VL* and VL15 CSRs are bit-wise identical */ + read_one_cm_vl(ppd, dd->params->send_cm_credit_vl15_reg, &bc->vl[15]); + + reg = read_eport_csr(dd, ppd->hw_pidx, + dd->params->send_cm_global_credit_reg); + bc->overall_shared_limit = + cpu_to_be16((reg >> SEND_CM_GLOBAL_CREDIT_SHARED_LIMIT_SHIFT) & + SEND_CM_GLOBAL_CREDIT_SHARED_LIMIT_MASK); + if (overall_limit) + *overall_limit = + (reg >> + SEND_CM_GLOBAL_CREDIT_TOTAL_CREDIT_LIMIT_SHIFT) & + SEND_CM_GLOBAL_CREDIT_TOTAL_CREDIT_LIMIT_MASK; + return sizeof(struct buffer_control); +} + +static int get_sc2vlnt(struct hfi2_devdata *dd, struct sc2vlnt *dp) +{ + u64 reg; + int i; + + /* each register contains 16 SC->VLnt mappings, 4 bits each */ + reg = hfi2_read_csr(dd, DCC_CFG_SC_VL_TABLE_15_0); + for (i = 0; i < sizeof(u64); i++) { + u8 byte = *(((u8 *)®) + i); + + dp->vlnt[2 * i] = byte & 0xf; + dp->vlnt[(2 * i) + 1] = (byte & 0xf0) >> 4; + } + + reg = hfi2_read_csr(dd, DCC_CFG_SC_VL_TABLE_31_16); + for (i = 0; i < sizeof(u64); i++) { + u8 byte = *(((u8 *)®) + i); + + dp->vlnt[16 + (2 * i)] = byte & 0xf; + dp->vlnt[16 + (2 * i) + 1] = (byte & 0xf0) >> 4; + } + return sizeof(struct sc2vlnt); +} + +static void get_vlarb_preempt(struct hfi2_devdata *dd, u32 nelems, + struct ib_vl_weight_elem *vl) +{ + unsigned int i; + + for (i = 0; i < nelems; i++, vl++) { + vl->vl = 0xf; + vl->weight = 0; + } +} + +static void set_sc2vlnt(struct hfi2_devdata *dd, struct sc2vlnt *dp) +{ + hfi2_write_csr( + dd, DCC_CFG_SC_VL_TABLE_15_0, + DC_SC_VL_VAL(15_0, 0, dp->vlnt[0] & 0xf, 1, dp->vlnt[1] & 0xf, + 2, dp->vlnt[2] & 0xf, 3, dp->vlnt[3] & 0xf, 4, + dp->vlnt[4] & 0xf, 5, dp->vlnt[5] & 0xf, 6, + dp->vlnt[6] & 0xf, 7, dp->vlnt[7] & 0xf, 8, + dp->vlnt[8] & 0xf, 9, dp->vlnt[9] & 0xf, 10, + dp->vlnt[10] & 0xf, 11, dp->vlnt[11] & 0xf, 12, + dp->vlnt[12] & 0xf, 13, dp->vlnt[13] & 0xf, 14, + dp->vlnt[14] & 0xf, 15, dp->vlnt[15] & 0xf)); + hfi2_write_csr( + dd, DCC_CFG_SC_VL_TABLE_31_16, + DC_SC_VL_VAL(31_16, 16, dp->vlnt[16] & 0xf, 17, + dp->vlnt[17] & 0xf, 18, dp->vlnt[18] & 0xf, 19, + dp->vlnt[19] & 0xf, 20, dp->vlnt[20] & 0xf, 21, + dp->vlnt[21] & 0xf, 22, dp->vlnt[22] & 0xf, 23, + dp->vlnt[23] & 0xf, 24, dp->vlnt[24] & 0xf, 25, + dp->vlnt[25] & 0xf, 26, dp->vlnt[26] & 0xf, 27, + dp->vlnt[27] & 0xf, 28, dp->vlnt[28] & 0xf, 29, + dp->vlnt[29] & 0xf, 30, dp->vlnt[30] & 0xf, 31, + dp->vlnt[31] & 0xf)); +} + +static void nonzero_msg(struct hfi2_devdata *dd, int idx, const char *what, + u16 limit) +{ + if (limit != 0) + dd_dev_info(dd, "Invalid %s limit %d on VL %d, ignoring\n", + what, (int)limit, idx); +} + +/* change only the shared limit portion of SendCmGLobalCredit */ +static void set_global_shared(struct hfi2_pportdata *ppd, u16 limit) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + + reg = read_eport_csr(dd, ppd->hw_pidx, + dd->params->send_cm_global_credit_reg); + reg &= ~SEND_CM_GLOBAL_CREDIT_SHARED_LIMIT_SMASK; + reg |= (u64)limit << SEND_CM_GLOBAL_CREDIT_SHARED_LIMIT_SHIFT; + write_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_global_credit_reg, + reg); +} + +/* change only the total credit limit portion of SendCmGLobalCredit */ +static void set_global_limit(struct hfi2_pportdata *ppd, u16 limit) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + + reg = read_eport_csr(dd, ppd->hw_pidx, + dd->params->send_cm_global_credit_reg); + reg &= ~SEND_CM_GLOBAL_CREDIT_TOTAL_CREDIT_LIMIT_SMASK; + reg |= (u64)limit << SEND_CM_GLOBAL_CREDIT_TOTAL_CREDIT_LIMIT_SHIFT; + write_eport_csr(dd, ppd->hw_pidx, dd->params->send_cm_global_credit_reg, + reg); +} + +/* set the given per-VL shared limit */ +static void set_vl_shared(struct hfi2_pportdata *ppd, int vl, u16 limit) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + u32 addr; + + if (vl < TXE_NUM_DATA_VL) + addr = dd->params->send_cm_credit_vl_reg + (8 * vl); + else + addr = dd->params->send_cm_credit_vl15_reg; + + reg = read_eport_csr(dd, ppd->hw_pidx, addr); + reg &= ~SEND_CM_CREDIT_VL_SHARED_LIMIT_VL_SMASK; + reg |= (u64)limit << SEND_CM_CREDIT_VL_SHARED_LIMIT_VL_SHIFT; + write_eport_csr(dd, ppd->hw_pidx, addr, reg); +} + +/* set the given per-VL dedicated limit */ +static void set_vl_dedicated(struct hfi2_pportdata *ppd, int vl, u16 limit) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + u32 addr; + + if (vl < TXE_NUM_DATA_VL) + addr = dd->params->send_cm_credit_vl_reg + (8 * vl); + else + addr = dd->params->send_cm_credit_vl15_reg; + + reg = read_eport_csr(dd, ppd->hw_pidx, addr); + reg &= ~SEND_CM_CREDIT_VL_DEDICATED_LIMIT_VL_SMASK; + reg |= (u64)limit << SEND_CM_CREDIT_VL_DEDICATED_LIMIT_VL_SHIFT; + write_eport_csr(dd, ppd->hw_pidx, addr, reg); +} + +/* spin until the given per-VL status mask bits clear */ +static void wait_for_vl_status_clear(struct hfi2_pportdata *ppd, u64 mask, + const char *which) +{ + struct hfi2_devdata *dd = ppd->dd; + unsigned long timeout; + u64 reg; + + timeout = jiffies + msecs_to_jiffies(VL_STATUS_CLEAR_TIMEOUT); + while (1) { + reg = read_eport_csr( + dd, ppd->hw_pidx, + dd->params->send_cm_credit_used_status_reg) & + mask; + + if (reg == 0) + return; /* success */ + if (time_after(jiffies, timeout)) + break; /* timed out */ + udelay(1); + } + + ppd_dev_err( + ppd, + "%s credit change status not clearing after %dms, mask 0x%llx, not clear 0x%llx\n", + which, VL_STATUS_CLEAR_TIMEOUT, mask, reg); + /* + * If this occurs, it is likely there was a credit loss on the link. + * The only recovery from that is a link bounce. + */ + ppd_dev_err( + ppd, + "Continuing anyway. A credit loss may occur. Suggest a link bounce\n"); +} + +/* + * The number of credits on the VLs may be changed while everything + * is "live", but the following algorithm must be followed due to + * how the hardware is actually implemented. In particular, + * Return_Credit_Status[] is the only correct status check. + * + * if (reducing Global_Shared_Credit_Limit or any shared limit changing) + * set Global_Shared_Credit_Limit = 0 + * use_all_vl = 1 + * mask0 = all VLs that are changing either dedicated or shared limits + * set Shared_Limit[mask0] = 0 + * spin until Return_Credit_Status[use_all_vl ? all VL : mask0] == 0 + * if (changing any dedicated limit) + * mask1 = all VLs that are lowering dedicated limits + * lower Dedicated_Limit[mask1] + * spin until Return_Credit_Status[mask1] == 0 + * raise Dedicated_Limits + * raise Shared_Limits + * raise Global_Shared_Credit_Limit + * + * lower = if the new limit is lower, set the limit to the new value + * raise = if the new limit is higher than the current value (may be changed + * earlier in the algorithm), set the new limit to the new value + */ +int hfi2_set_buffer_control(struct hfi2_pportdata *ppd, + struct buffer_control *new_bc) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 changing_mask, ld_mask, stat_mask; + int change_count; + int i, use_all_mask; + int this_shared_changing; + int vl_count = 0, ret; + /* + * A0: add the variable any_shared_limit_changing below and in the + * algorithm above. If removing A0 support, it can be removed. + */ + int any_shared_limit_changing; + struct buffer_control cur_bc; + u8 changing[OPA_MAX_VLS]; + u8 lowering_dedicated[OPA_MAX_VLS]; + u16 cur_total; + u32 new_total = 0; + const u64 all_mask = + SEND_CM_CREDIT_USED_STATUS_VL0_RETURN_CREDIT_STATUS_SMASK | + SEND_CM_CREDIT_USED_STATUS_VL1_RETURN_CREDIT_STATUS_SMASK | + SEND_CM_CREDIT_USED_STATUS_VL2_RETURN_CREDIT_STATUS_SMASK | + SEND_CM_CREDIT_USED_STATUS_VL3_RETURN_CREDIT_STATUS_SMASK | + SEND_CM_CREDIT_USED_STATUS_VL4_RETURN_CREDIT_STATUS_SMASK | + SEND_CM_CREDIT_USED_STATUS_VL5_RETURN_CREDIT_STATUS_SMASK | + SEND_CM_CREDIT_USED_STATUS_VL6_RETURN_CREDIT_STATUS_SMASK | + SEND_CM_CREDIT_USED_STATUS_VL7_RETURN_CREDIT_STATUS_SMASK | + SEND_CM_CREDIT_USED_STATUS_VL15_RETURN_CREDIT_STATUS_SMASK; + +#define valid_vl(idx) ((idx) < TXE_NUM_DATA_VL || (idx) == 15) +#define NUM_USABLE_VLS 16 /* look at VL15 and less */ + + /* find the new total credits, do sanity check on unused VLs */ + for (i = 0; i < OPA_MAX_VLS; i++) { + if (valid_vl(i)) { + new_total += be16_to_cpu(new_bc->vl[i].dedicated); + continue; + } + nonzero_msg(dd, i, "dedicated", + be16_to_cpu(new_bc->vl[i].dedicated)); + nonzero_msg(dd, i, "shared", be16_to_cpu(new_bc->vl[i].shared)); + new_bc->vl[i].dedicated = 0; + new_bc->vl[i].shared = 0; + } + new_total += be16_to_cpu(new_bc->overall_shared_limit); + + /* fetch the current values */ + get_buffer_control(ppd, &cur_bc, &cur_total); + + /* + * Create the masks we will use. + */ + memset(changing, 0, sizeof(changing)); + memset(lowering_dedicated, 0, sizeof(lowering_dedicated)); + /* + * NOTE: Assumes that the individual VL bits are adjacent and in + * increasing order + */ + stat_mask = SEND_CM_CREDIT_USED_STATUS_VL0_RETURN_CREDIT_STATUS_SMASK; + changing_mask = 0; + ld_mask = 0; + change_count = 0; + any_shared_limit_changing = 0; + for (i = 0; i < NUM_USABLE_VLS; i++, stat_mask <<= 1) { + if (!valid_vl(i)) + continue; + this_shared_changing = new_bc->vl[i].shared != + cur_bc.vl[i].shared; + if (this_shared_changing) + any_shared_limit_changing = 1; + if (new_bc->vl[i].dedicated != cur_bc.vl[i].dedicated || + this_shared_changing) { + changing[i] = 1; + changing_mask |= stat_mask; + change_count++; + } + if (be16_to_cpu(new_bc->vl[i].dedicated) < + be16_to_cpu(cur_bc.vl[i].dedicated)) { + lowering_dedicated[i] = 1; + ld_mask |= stat_mask; + } + } + + /* bracket the credit change with a total adjustment */ + if (new_total > cur_total) + set_global_limit(ppd, new_total); + + /* + * Start the credit change algorithm. + */ + use_all_mask = 0; + if ((be16_to_cpu(new_bc->overall_shared_limit) < + be16_to_cpu(cur_bc.overall_shared_limit)) || + (hfi2_is_ax(dd) && any_shared_limit_changing)) { + set_global_shared(ppd, 0); + cur_bc.overall_shared_limit = 0; + use_all_mask = 1; + } + + for (i = 0; i < NUM_USABLE_VLS; i++) { + if (!valid_vl(i)) + continue; + + if (changing[i]) { + set_vl_shared(ppd, i, 0); + cur_bc.vl[i].shared = 0; + } + } + + wait_for_vl_status_clear(ppd, use_all_mask ? all_mask : changing_mask, + "shared"); + + if (change_count > 0) { + for (i = 0; i < NUM_USABLE_VLS; i++) { + if (!valid_vl(i)) + continue; + + if (lowering_dedicated[i]) { + set_vl_dedicated( + ppd, i, + be16_to_cpu(new_bc->vl[i].dedicated)); + cur_bc.vl[i].dedicated = + new_bc->vl[i].dedicated; + } + } + + wait_for_vl_status_clear(ppd, ld_mask, "dedicated"); + + /* now raise all dedicated that are going up */ + for (i = 0; i < NUM_USABLE_VLS; i++) { + if (!valid_vl(i)) + continue; + + if (be16_to_cpu(new_bc->vl[i].dedicated) > + be16_to_cpu(cur_bc.vl[i].dedicated)) + set_vl_dedicated( + ppd, i, + be16_to_cpu(new_bc->vl[i].dedicated)); + } + } + + /* next raise all shared that are going up */ + for (i = 0; i < NUM_USABLE_VLS; i++) { + if (!valid_vl(i)) + continue; + + if (be16_to_cpu(new_bc->vl[i].shared) > + be16_to_cpu(cur_bc.vl[i].shared)) + set_vl_shared(ppd, i, + be16_to_cpu(new_bc->vl[i].shared)); + } + + /* finally raise the global shared */ + if (be16_to_cpu(new_bc->overall_shared_limit) > + be16_to_cpu(cur_bc.overall_shared_limit)) + set_global_shared(ppd, + be16_to_cpu(new_bc->overall_shared_limit)); + + /* bracket the credit change with a total adjustment */ + if (new_total < cur_total) + set_global_limit(ppd, new_total); + + /* + * Determine the actual number of operational VLS using the number of + * dedicated and shared credits for each VL. + */ + if (change_count > 0) { + for (i = 0; i < TXE_NUM_DATA_VL; i++) + if (be16_to_cpu(new_bc->vl[i].dedicated) > 0 || + be16_to_cpu(new_bc->vl[i].shared) > 0) + vl_count++; + ppd->actual_vls_operational = vl_count; + ret = hfi2_sdma_map_init(ppd, + vl_count ? + ppd->actual_vls_operational : + ppd->vls_operational, + NULL); + if (ret == 0) + ret = hfi2_pio_map_init( + ppd, vl_count ? ppd->actual_vls_operational : + ppd->vls_operational); + if (ret) + return ret; + } + return 0; +} + +/* + * Read the given fabric manager table. Return the size of the + * table (in bytes) on success, and a negative error code on + * failure. + */ +int hfi2_fm_get_table(struct hfi2_pportdata *ppd, int which, void *t) + +{ + int size; + struct vl_arb_cache *vlc; + + switch (which) { + case FM_TBL_VL_HIGH_ARB: + size = 256; + /* + * OPA specifies 128 elements (of 2 bytes each), though + * HFI supports only 16 elements in h/w. + */ + vlc = vl_arb_lock_cache(ppd, HI_PRIO_TABLE); + vl_arb_get_cache(vlc, t); + vl_arb_unlock_cache(ppd, HI_PRIO_TABLE); + break; + case FM_TBL_VL_LOW_ARB: + size = 256; + /* + * OPA specifies 128 elements (of 2 bytes each), though + * HFI supports only 16 elements in h/w. + */ + vlc = vl_arb_lock_cache(ppd, LO_PRIO_TABLE); + vl_arb_get_cache(vlc, t); + vl_arb_unlock_cache(ppd, LO_PRIO_TABLE); + break; + case FM_TBL_BUFFER_CONTROL: + size = get_buffer_control(ppd, t, NULL); + break; + case FM_TBL_SC2VLNT: + size = get_sc2vlnt(ppd->dd, t); + break; + case FM_TBL_VL_PREEMPT_ELEMS: + size = 256; + /* OPA specifies 128 elements, of 2 bytes each */ + get_vlarb_preempt(ppd->dd, OPA_MAX_VLS, t); + break; + case FM_TBL_VL_PREEMPT_MATRIX: + size = 256; + /* + * OPA specifies that this is the same size as the VL + * arbitration tables (i.e., 256 bytes). + */ + break; + default: + return -EINVAL; + } + return size; +} + +/* + * Write the given fabric manager table. + */ +int hfi2_fm_set_table(struct hfi2_pportdata *ppd, int which, void *t) +{ + int ret = 0; + struct vl_arb_cache *vlc; + + switch (which) { + case FM_TBL_VL_HIGH_ARB: + vlc = vl_arb_lock_cache(ppd, HI_PRIO_TABLE); + if (vl_arb_match_cache(vlc, t)) { + vl_arb_unlock_cache(ppd, HI_PRIO_TABLE); + break; + } + vl_arb_set_cache(vlc, t); + vl_arb_unlock_cache(ppd, HI_PRIO_TABLE); + ret = set_vl_weights( + ppd, ppd->dd->params->send_high_priority_list_reg, + VL_ARB_HIGH_PRIO_TABLE_SIZE, t); + break; + case FM_TBL_VL_LOW_ARB: + vlc = vl_arb_lock_cache(ppd, LO_PRIO_TABLE); + if (vl_arb_match_cache(vlc, t)) { + vl_arb_unlock_cache(ppd, LO_PRIO_TABLE); + break; + } + vl_arb_set_cache(vlc, t); + vl_arb_unlock_cache(ppd, LO_PRIO_TABLE); + ret = set_vl_weights( + ppd, ppd->dd->params->send_low_priority_list_reg, + VL_ARB_LOW_PRIO_TABLE_SIZE, t); + break; + case FM_TBL_BUFFER_CONTROL: + ret = hfi2_set_buffer_control(ppd, t); + break; + case FM_TBL_SC2VLNT: + set_sc2vlnt(ppd->dd, t); + break; + default: + ret = -EINVAL; + } + return ret; +} + +/* + * Disable all data VLs. + * + * Return 0 if disabled, non-zero if the VLs cannot be disabled. + */ +static int disable_data_vls(struct hfi2_pportdata *ppd) +{ + if (hfi2_is_ax(ppd->dd)) + return 1; + + hfi2_pio_send_control(ppd, PSC_DATA_VL_DISABLE); + + return 0; +} + +/* + * hfi2_open_fill_data_vls() - the counterpart to hfi2_stop_drain_data_vls(). + * Just re-enables all data VLs (the "fill" part happens + * automatically - the name was chosen for symmetry with + * hfi2_stop_drain_data_vls()). + * + * Return 0 if successful, non-zero if the VLs cannot be enabled. + */ +int hfi2_open_fill_data_vls(struct hfi2_pportdata *ppd) +{ + if (hfi2_is_ax(ppd->dd)) + return 1; + + hfi2_pio_send_control(ppd, PSC_DATA_VL_ENABLE); + + return 0; +} + +/* + * drain_data_vls() - assumes that disable_data_vls() has been called, + * wait for occupancy (of per-VL FIFOs) for all contexts, and SDMA + * engines to drop to 0. + */ +static void drain_data_vls(struct hfi2_devdata *dd) +{ + hfi2_sc_wait(dd); + hfi2_sdma_wait(dd); + pause_for_credit_return(dd); +} + +/* + * hfi2_stop_drain_data_vls() - disable, then drain all per-VL fifos. + * + * Use hfi2_open_fill_data_vls() to resume using data VLs. This pair is + * meant to be used like this: + * + * hfi2_stop_drain_data_vls(dd); + * // do things with per-VL resources + * hfi2_open_fill_data_vls(dd); + */ +int hfi2_stop_drain_data_vls(struct hfi2_pportdata *ppd) +{ + int ret; + + ret = disable_data_vls(ppd); + if (ret == 0) + drain_data_vls(ppd->dd); + + return ret; +} + +/* + * Convert a nanosecond time to a cclock count. No matter how slow + * the cclock, a non-zero ns will always have a non-zero result. + */ +u32 hfi2_ns_to_cclock(struct hfi2_devdata *dd, u32 ns) +{ + u32 cclocks; + + /* simulation pretends to be ASIC */ + cclocks = (ns * 1000) / dd->params->asic_cclock_ps; + if (ns && !cclocks) /* if ns nonzero, must be at least 1 */ + cclocks = 1; + return cclocks; +} + +/* + * Convert a cclock count to nanoseconds. No matter how slow + * the cclock, a non-zero cclocks will always have a non-zero result. + */ +u32 hfi2_cclock_to_ns(struct hfi2_devdata *dd, u32 cclocks) +{ + u32 ns; + + /* simulation pretends to be ASIC */ + ns = (cclocks * dd->params->asic_cclock_ps) / 1000; + if (cclocks && !ns) + ns = 1; + return ns; +} + +/* + * Dynamically adjust the receive interrupt timeout for a context based on + * incoming packet rate. + * + * NOTE: Dynamic adjustment does not allow hfi2_rcv_intr_count to be zero. + */ +static void adjust_rcv_timeout(struct hfi2_ctxtdata *rcd, u32 npkts) +{ + struct hfi2_devdata *dd = rcd->dd; + u32 timeout = rcd->rcvavail_timeout; + + /* + * This algorithm doubles or halves the timeout depending on whether + * the number of packets received in this interrupt were less than or + * greater equal the interrupt count. + * + * The calculations below do not allow a steady state to be achieved. + * Only at the endpoints it is possible to have an unchanging + * timeout. + */ + if (npkts < hfi2_rcv_intr_count) { + /* + * Not enough packets arrived before the timeout, adjust + * timeout downward. + */ + if (timeout < 2) /* already at minimum? */ + return; + timeout >>= 1; + } else { + /* + * More than enough packets arrived before the timeout, adjust + * timeout upward. + */ + if (timeout >= dd->rcv_intr_timeout_csr) /* already at max? */ + return; + timeout = min(timeout << 1, dd->rcv_intr_timeout_csr); + } + + rcd->rcvavail_timeout = timeout; + /* + * timeout cannot be larger than rcv_intr_timeout_csr which has already + * been verified to be in range + */ + write_kctxt_csr(dd, rcd->ctxt, dd->params->rcv_avail_time_out_reg, + (u64)timeout + << RCV_AVAIL_TIME_OUT_TIME_OUT_RELOAD_SHIFT); +} + +void hfi2_update_usrhead(struct hfi2_ctxtdata *rcd, u32 hd, u32 updegr, + u32 egrhd, u32 intr_adjust, u32 npkts) +{ + struct hfi2_devdata *dd = rcd->dd; + u32 ctxt = rcd->ctxt; + + /* + * Need to write timeout register before updating RcvHdrHead to ensure + * that a new value is used when the HW decides to restart counting. + */ + if (intr_adjust) + adjust_rcv_timeout(rcd, npkts); + hfi2_update_usrhead_ctxt(dd, ctxt, hd, hfi2_rcv_intr_count, updegr, + egrhd); +} + +void hfi2_update_usrhead_ctxt(struct hfi2_devdata *dd, u16 ctxt, u32 hd, + u32 intr_cnt, u32 updegr, u32 egrhd) +{ + u64 reg; + + if (updegr) { + reg = (egrhd & RCV_EGR_INDEX_HEAD_HEAD_MASK) + << RCV_EGR_INDEX_HEAD_HEAD_SHIFT; + write_uctxt_csr(dd, ctxt, dd->params->rcv_egr_index_head_reg, + reg); + } + reg = ((u64)intr_cnt << RCV_HDR_HEAD_COUNTER_SHIFT) | + (((u64)hd & RCV_HDR_HEAD_HEAD_MASK) << RCV_HDR_HEAD_HEAD_SHIFT); + write_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg, reg); +} + +u32 hfi2_hdrqempty(struct hfi2_ctxtdata *rcd) +{ + u32 head, tail; + + head = (read_uctxt_csr(rcd->dd, rcd->ctxt, + rcd->dd->params->rcv_hdr_head_reg) & + RCV_HDR_HEAD_HEAD_SMASK) >> + RCV_HDR_HEAD_HEAD_SHIFT; + + if (hfi2_rcvhdrtail_kvaddr(rcd)) + tail = get_rcvhdrtail(rcd); + else + tail = read_uctxt_csr(rcd->dd, rcd->ctxt, + rcd->dd->params->rcv_hdr_tail_reg); + + return head == tail; +} + +/* + * Context Control and Receive Array encoding for buffer size: + * 0x0 invalid + * 0x1 4 KB + * 0x2 8 KB + * 0x3 16 KB + * 0x4 32 KB + * 0x5 64 KB + * 0x6 128 KB + * 0x7 256 KB + * 0x8 512 KB (Receive Array only) + * 0x9 1 MB (Receive Array only) + * 0xa 2 MB (Receive Array only) + * + * 0xB-0xF - reserved (Receive Array only) + * + * + * This routine assumes that the value has already been sanity checked. + */ +u32 hfi2_encoded_size(u32 size) +{ + switch (size) { + case 4 * 1024: + return 0x1; + case 8 * 1024: + return 0x2; + case 16 * 1024: + return 0x3; + case 32 * 1024: + return 0x4; + case 64 * 1024: + return 0x5; + case 128 * 1024: + return 0x6; + case 256 * 1024: + return 0x7; + case 512 * 1024: + return 0x8; + case 1 * 1024 * 1024: + return 0x9; + case 2 * 1024 * 1024: + return 0xa; + } + return 0x1; /* if invalid, go with the minimum size */ +} + +/** + * hfi2_encode_rcv_header_entry_size - return chip specific encoding for size + * @size: size in dwords + * + * Convert a receive header entry size that to the encoding used in the CSR. + * + * Return a zero if the given size is invalid, otherwise the encoding. + */ +u8 hfi2_encode_rcv_header_entry_size(u8 size) +{ + /* there are only 3 valid receive header entry sizes */ + if (size == 2) + return 1; + if (size == 16) + return 2; + if (size == 32) + return 4; + return 0; /* invalid */ +} + +/** + * hfi2_validate_rcvhdrcnt - validate hdrcnt + * @dd: the device data + * @thecnt: the header count + */ +int hfi2_validate_rcvhdrcnt(struct hfi2_devdata *dd, uint thecnt) +{ + if (thecnt <= HFI2_MIN_HDRQ_EGRBUF_CNT) { + dd_dev_err(dd, "Receive header queue count too small\n"); + return -EINVAL; + } + + if (thecnt > HFI2_MAX_HDRQ_EGRBUF_CNT) { + dd_dev_err( + dd, + "Receive header queue count cannot be greater than %u\n", + HFI2_MAX_HDRQ_EGRBUF_CNT); + return -EINVAL; + } + + if (thecnt % HDRQ_INCREMENT) { + dd_dev_err( + dd, + "Receive header queue count %d must be divisible by %lu\n", + thecnt, HDRQ_INCREMENT); + return -EINVAL; + } + + return 0; +} + +void hfi2_wfr_update_rcv_hdr_size(struct hfi2_pportdata *ppd, u16 ctxt, + u32 size) +{ + u64 reg; + + reg = ((u64)size & RCV_HDR_SIZE_HDR_SIZE_MASK) + << RCV_HDR_SIZE_HDR_SIZE_SHIFT; + write_kctxt_csr(ppd->dd, ctxt, RCV_HDR_SIZE, reg); +} + +/** + * hfi2_set_hdrq_regs - set header queue registers for context + * @ppd: the port data + * @ctxt: the context + * @entsize: the dword entry size + * @hdrcnt: the number of header entries + * @kdeth_rcv_hdr: KDETH receive header size + */ +void hfi2_set_hdrq_regs(struct hfi2_pportdata *ppd, u16 ctxt, u8 entsize, + u16 hdrcnt, u8 kdeth_rcv_hdr) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + + reg = (((u64)hdrcnt >> HDRQ_SIZE_SHIFT) & RCV_HDR_CNT_CNT_MASK) + << RCV_HDR_CNT_CNT_SHIFT; + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_cnt_reg, reg); + reg = ((u64)hfi2_encode_rcv_header_entry_size(entsize) & + RCV_HDR_ENT_SIZE_ENT_SIZE_MASK) + << RCV_HDR_ENT_SIZE_ENT_SIZE_SHIFT; + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_ent_size_reg, reg); + dd->params->update_rcv_hdr_size(ppd, ctxt, kdeth_rcv_hdr); + + /* + * Program dummy tail address for every receive context + * before enabling any receive context + */ + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_tail_addr_reg, + dd->rcvhdrtail_dummy_dma); +} + +/* this is a type of kernel context */ +bool hfi2_is_control_context(struct hfi2_ctxtdata *rcd) +{ + return rcd->ctxt == + rcd->ppd->dd->rsrcs.ppr[rcd->ppd->hw_pidx].rcv_context_base + + HFI2_CTRL_CTXT; +} + +/* includes control context */ +bool hfi2_is_kernel_context(struct hfi2_ctxtdata *rcd) +{ + /* assumes in sequential order from base */ + return rcd->ctxt < + rcd->ppd->dd->rsrcs.ppr[rcd->ppd->hw_pidx].first_dyn_alloc_ctxt; +} + +/* includes user contexts */ +bool hfi2_is_dynamic_context(struct hfi2_ctxtdata *rcd) +{ + struct hfi2_pportdata *ppd = rcd->ppd; + struct hfi2_devrsrcs *dr = &ppd->dd->rsrcs; + struct hfi2_portrsrcs *pr = &dr->ppr[ppd->hw_pidx]; + + return rcd->ctxt >= pr->first_dyn_alloc_ctxt && + rcd->ctxt < (pr->rcv_context_base + pr->num_rcv_contexts); +} + +bool hfi2_is_user_context(struct hfi2_ctxtdata *rcd) +{ + return hfi2_is_dynamic_context(rcd); +} + +/* WFR specific rcv context enable, disable */ +void hfi2_wfr_enable_rcv_context(struct hfi2_pportdata *ppd, u16 ctxt, + u64 *kctxt_ctrl, bool enable) +{ + /* nothing special needs to be done */ +} + +u64 hfi2_rctxt_ctrl_op(struct hfi2_devdata *dd, u16 ctxt, unsigned int op) +{ + u64 rctxt_ctrl; + + if (dd->is_vf) + return hfi2_pf0_rctxt_ctrl_op(dd, ctxt, op); + + rctxt_ctrl = read_rctxt_csr(dd, ctxt, dd->params->rcv_rctxt_ctrl_reg); + if (op & HFI2_RCVCTRL_INTRAVAIL_ENB) + rctxt_ctrl |= RCV_CTXT_CTRL_INTR_AVAIL_SMASK; + if (op & HFI2_RCVCTRL_INTRAVAIL_DIS) + rctxt_ctrl &= ~RCV_CTXT_CTRL_INTR_AVAIL_SMASK; + if (op & HFI2_RCVCTRL_TIDFLOW_ENB) + rctxt_ctrl |= RCV_CTXT_CTRL_TID_FLOW_ENABLE_SMASK; + if (op & HFI2_RCVCTRL_TIDFLOW_DIS) + rctxt_ctrl &= ~RCV_CTXT_CTRL_TID_FLOW_ENABLE_SMASK; + if (op & HFI2_RCVCTRL_NO_RHQ_DROP_ENB) + rctxt_ctrl |= RCV_CTXT_CTRL_DONT_DROP_RHQ_FULL_SMASK; + if (op & HFI2_RCVCTRL_NO_RHQ_DROP_DIS) + rctxt_ctrl &= ~RCV_CTXT_CTRL_DONT_DROP_RHQ_FULL_SMASK; + if (op & HFI2_RCVCTRL_NO_EGR_DROP_ENB) + rctxt_ctrl |= RCV_CTXT_CTRL_DONT_DROP_EGR_FULL_SMASK; + if (op & HFI2_RCVCTRL_NO_EGR_DROP_DIS) + rctxt_ctrl &= ~RCV_CTXT_CTRL_DONT_DROP_EGR_FULL_SMASK; + write_rctxt_csr(dd, ctxt, dd->params->rcv_rctxt_ctrl_reg, rctxt_ctrl); + return rctxt_ctrl; +} + +void hfi2_rcvctrl(struct hfi2_devdata *dd, unsigned int op, + struct hfi2_ctxtdata *rcd) +{ + u64 rcvctrl, reg; + u64 rctxt_ctrl; + int did_enable = 0; + u16 ctxt; + + if (!rcd) + return; + + ctxt = rcd->ctxt; + + hfi2_cdbg(RCVCTRL, "ctxt %d op 0x%x", ctxt, op); + + rcvctrl = read_kctxt_csr(dd, ctxt, dd->params->rcv_kctxt_ctrl_reg); + /* if the context already enabled, don't do the extra steps */ + if ((op & HFI2_RCVCTRL_CTXT_ENB) && + !(rcvctrl & RCV_CTXT_CTRL_ENABLE_SMASK)) { + /* reset the tail and hdr addresses, and sequence count */ + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_addr_reg, + rcd->rcvhdrq_dma); + if (hfi2_rcvhdrtail_kvaddr(rcd)) + write_kctxt_csr(dd, ctxt, + dd->params->rcv_hdr_tail_addr_reg, + rcd->rcvhdrqtailaddr_dma); + if (dd->params->set_rheq_addr) + dd->params->set_rheq_addr(dd, ctxt, rcd->rheq_dma); + hfi2_set_seq_cnt(rcd, 1); + + /* reset the cached receive header queue head value */ + hfi2_set_rcd_head(rcd, 0); + + /* + * Zero the receive header queue so we don't get false + * positives when checking the sequence number. The + * sequence numbers could land exactly on the same spot. + * E.g. a rcd restart before the receive header wrapped. + */ + memset(rcd->rcvhdrq, 0, rcvhdrq_size(rcd)); + + /* starting timeout */ + rcd->rcvavail_timeout = dd->rcv_intr_timeout_csr; + + /* enable the context */ + rcvctrl |= RCV_CTXT_CTRL_ENABLE_SMASK; + + /* clean the egr buffer size first */ + rcvctrl &= ~RCV_CTXT_CTRL_EGR_BUF_SIZE_SMASK; + rcvctrl |= ((u64)hfi2_encoded_size(rcd->egrbufs.rcvtid_size) & + RCV_CTXT_CTRL_EGR_BUF_SIZE_MASK) + << RCV_CTXT_CTRL_EGR_BUF_SIZE_SHIFT; + + /* zero RcvHdrHead - set RcvHdrHead.Counter after enable */ + write_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg, 0); + did_enable = 1; + + /* zero RcvEgrIndexHead */ + write_uctxt_csr(dd, ctxt, dd->params->rcv_egr_index_head_reg, + 0); + + /* WFR only: direct VL15 packets to the control context */ + if (hfi2_is_control_context(rcd) && + dd->params->chip_type == CHIP_WFR) { + write_iport_csr(dd, rcd->ppd->hw_pidx, + dd->params->rcv_vl15_reg, ctxt); + } + + /* per-chip enable */ + dd->params->enable_rcv_context(rcd->ppd, ctxt, &rcvctrl, true); + } + if (op & HFI2_RCVCTRL_CTXT_DIS) { + /* + * When receive context is being disabled turn on tail + * update with a dummy tail address and then disable + * receive context. + */ + if (dd->rcvhdrtail_dummy_dma) { + write_kctxt_csr(dd, ctxt, + dd->params->rcv_hdr_tail_addr_reg, + dd->rcvhdrtail_dummy_dma); + /* Enabling RcvCtxtCtrl.TailUpd is intentional. */ + rcvctrl |= RCV_CTXT_CTRL_TAIL_UPD_SMASK; + } + + rcvctrl &= ~RCV_CTXT_CTRL_ENABLE_SMASK; + + /* per-chip disable */ + dd->params->enable_rcv_context(rcd->ppd, ctxt, &rcvctrl, false); + } + if ((op & HFI2_RCVCTRL_TAILUPD_ENB) && hfi2_rcvhdrtail_kvaddr(rcd)) + rcvctrl |= RCV_CTXT_CTRL_TAIL_UPD_SMASK; + if (op & HFI2_RCVCTRL_TAILUPD_DIS) { + /* See comment on RcvCtxtCtrl.TailUpd above */ + if (!(op & HFI2_RCVCTRL_CTXT_DIS)) + rcvctrl &= ~RCV_CTXT_CTRL_TAIL_UPD_SMASK; + } + if (op & HFI2_RCVCTRL_ONE_PKT_EGR_ENB) { + /* + * In one-packet-per-eager mode, the size comes from + * the RcvArray entry. + */ + rcvctrl &= ~RCV_CTXT_CTRL_EGR_BUF_SIZE_SMASK; + rcvctrl |= RCV_CTXT_CTRL_ONE_PACKET_PER_EGR_BUFFER_SMASK; + } + if (op & HFI2_RCVCTRL_ONE_PKT_EGR_DIS) + rcvctrl &= ~RCV_CTXT_CTRL_ONE_PACKET_PER_EGR_BUFFER_SMASK; + if (op & HFI2_RCVCTRL_URGENT_ENB) + hfi2_set_intr_bits(dd, dd->params->is_rcvurgent_start + ctxt, + dd->params->is_rcvurgent_start + ctxt, true); + if (op & HFI2_RCVCTRL_URGENT_DIS) + hfi2_set_intr_bits(dd, dd->params->is_rcvurgent_start + ctxt, + dd->params->is_rcvurgent_start + ctxt, + false); + + write_kctxt_csr(dd, ctxt, dd->params->rcv_kctxt_ctrl_reg, rcvctrl); + + if (op & HFI2_RCVCTRL_INTRAVAIL_ENB) + hfi2_set_intr_bits(dd, dd->params->is_rcvavail_start + ctxt, + dd->params->is_rcvavail_start + ctxt, true); + if (op & HFI2_RCVCTRL_INTRAVAIL_DIS) + hfi2_set_intr_bits(dd, dd->params->is_rcvavail_start + ctxt, + dd->params->is_rcvavail_start + ctxt, false); + rctxt_ctrl = hfi2_rctxt_ctrl_op(dd, ctxt, op); + hfi2_cdbg(RCVCTRL, "ctxt %d kctrl 0x%llx rctrl 0x%llx", ctxt, rcvctrl, + rctxt_ctrl); + + /* work around sticky RcvCtxtStatus.BlockedRHQFull */ + if (did_enable && + (rctxt_ctrl & RCV_CTXT_CTRL_DONT_DROP_RHQ_FULL_SMASK)) { + reg = read_ku_csr(dd, ctxt, dd->params->rcv_ctxt_status_reg); + if (reg != 0) { + dd_dev_info(dd, "ctxt %d status %lld (blocked)\n", ctxt, + reg); + read_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg); + write_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg, + 0x10); + write_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg, + 0x00); + read_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg); + reg = read_ku_csr(dd, ctxt, + dd->params->rcv_ctxt_status_reg); + dd_dev_info(dd, "ctxt %d status %lld (%s blocked)\n", + ctxt, reg, reg == 0 ? "not" : "still"); + } + } + + if (did_enable) { + /* + * The interrupt timeout and count must be set after + * the context is enabled to take effect. + */ + /* set interrupt timeout */ + write_kctxt_csr( + dd, ctxt, dd->params->rcv_avail_time_out_reg, + (u64)rcd->rcvavail_timeout + << RCV_AVAIL_TIME_OUT_TIME_OUT_RELOAD_SHIFT); + + /* set RcvHdrHead.Counter, zero RcvHdrHead.Head (again) */ + reg = (u64)hfi2_rcv_intr_count << RCV_HDR_HEAD_COUNTER_SHIFT; + write_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg, reg); + } + + if (op & (HFI2_RCVCTRL_TAILUPD_DIS | HFI2_RCVCTRL_CTXT_DIS)) + /* + * If the context has been disabled and the Tail Update has + * been cleared, set the RCV_HDR_TAIL_ADDR CSR to dummy address + * so it doesn't contain an address that is invalid. + */ + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_tail_addr_reg, + dd->rcvhdrtail_dummy_dma); +} + +/* + * Read the counters from the table, store the values read into results. + */ +static void read_counters(struct hfi2_devdata *dd, + const struct cntr_entry *table, int table_size, + void *context, u64 *results) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + const struct cntr_entry *entry; + u64 val; + int i, j; + + /* fill in each counter from the table */ + for (i = 0; i < table_size; i++) { + entry = &table[i]; + hfi2_cdbg(CNTR, "reading %s", entry->name); + if (entry->flags & CNTR_DISABLED) { + /* Nothing */ + hfi2_cdbg(CNTR, "\tDisabled"); + continue; + } + + if (entry->flags & CNTR_VL) { + hfi2_cdbg(CNTR, "\tPer VL"); + for (j = 0; j < C_VL_COUNT; j++) { + val = entry->rw_cntr(entry, context, j, + CNTR_MODE_R, 0); + hfi2_cdbg(CNTR, "\t\tRead 0x%llx for %d", val, + j); + results[entry->offset + j] = val; + } + } else if (entry->flags & CNTR_SDMA) { + hfi2_cdbg(CNTR, "\tPer SDMA Engine"); + for (j = dr->first_sdma_engine; + j < dr->last_sdma_engine; j++) { + val = entry->rw_cntr(entry, context, j, + CNTR_MODE_R, 0); + hfi2_cdbg(CNTR, "\t\tRead 0x%llx for %d", val, + j); + results[entry->offset + j] = val; + } + } else if (entry->flags & CNTR_OVF) { + hfi2_cdbg(CNTR, "\tPer ctxt"); + for (j = 0; j < dd->num_rcd; j++) { + if (test_bit(j, dd->ovf_disabled)) + continue; + val = entry->rw_cntr(entry, context, j, + CNTR_MODE_R, 0); + hfi2_cdbg(CNTR, "\t\tRead 0x%llx for %d", val, + j); + results[entry->offset + dd->ovf_offset[j]] = + val; + } + } else { + val = entry->rw_cntr(entry, context, CNTR_INVALID_VL, + CNTR_MODE_R, 0); + results[entry->offset] = val; + hfi2_cdbg(CNTR, "\tRead 0x%llx", val); + } + } +} + +/* + * Return device counter names or updated counter values. Return buffer size. + * Used by sysfs and verbs. + */ +u32 hfi2_read_cntrs(struct hfi2_devdata *dd, char **namep, u64 **cntrp) +{ + if (namep) { + *namep = dd->cntrnames; + return dd->cntrnameslen; + } + if (dd->is_vf) { + goto skip; + } + + read_counters(dd, hfi2_shared_dev_cntrs, SHARED_DEV_CNTR_LAST, dd, + dd->cntrs); + read_counters(dd, dd->params->chip_dev_cntrs, + dd->params->chip_num_dev_cntrs, dd, dd->cntrs); +skip: + *cntrp = dd->cntrs; + return dd->ndevcntrs * sizeof(u64); +} + +/* + * Return port counter names or updated counter values. Return buffer size. + * Used by sysfs and verbs. + */ +u32 hfi2_read_portcntrs(struct hfi2_pportdata *ppd, char **namep, u64 **cntrp) +{ + struct hfi2_devdata *dd = ppd->dd; + + if (namep) { + *namep = dd->portcntrnames; + return dd->portcntrnameslen; + } + + read_counters(dd, hfi2_shared_port_cntrs, SHARED_PORT_CNTR_LAST, ppd, + ppd->cntrs); + read_counters(dd, dd->params->chip_port_cntrs, + dd->params->chip_num_port_cntrs, ppd, ppd->cntrs); + + *cntrp = ppd->cntrs; + return dd->nportcntrs * sizeof(u64); +} + +static void free_cntrs(struct hfi2_devdata *dd) +{ + struct hfi2_pportdata *ppd; + int i; + + if (dd->synth_stats_timer.function) + timer_delete_sync(&dd->synth_stats_timer); + if (dd->rcverr_timer.function) + timer_delete_sync(&dd->rcverr_timer); + cancel_work_sync(&dd->update_cntr_work); + cancel_work_sync(&dd->rcverr_work); + ppd = (struct hfi2_pportdata *)(dd + 1); + for (i = 0; i < dd->num_pports; i++, ppd++) { + kfree(ppd->cntrs); + kfree(ppd->scntrs); + free_percpu(ppd->ibport_data.rvp.rc_acks); + free_percpu(ppd->ibport_data.rvp.rc_qacks); + free_percpu(ppd->ibport_data.rvp.rc_delayed_comp); + ppd->cntrs = NULL; + ppd->scntrs = NULL; + ppd->ibport_data.rvp.rc_acks = NULL; + ppd->ibport_data.rvp.rc_qacks = NULL; + ppd->ibport_data.rvp.rc_delayed_comp = NULL; + } + kfree(dd->portcntrnames); + dd->portcntrnames = NULL; + kfree(dd->cntrs); + dd->cntrs = NULL; + kfree(dd->scntrs); + dd->scntrs = NULL; + kfree(dd->cntrnames); + dd->cntrnames = NULL; + if (dd->update_cntr_wq) { + destroy_workqueue(dd->update_cntr_wq); + dd->update_cntr_wq = NULL; + } +} + +static u64 read_dev_port_cntr(struct hfi2_devdata *dd, struct cntr_entry *entry, + u64 *psval, void *context, int vl) +{ + u64 val; + u64 sval = *psval; + + if (entry->flags & CNTR_DISABLED) { + dd_dev_err(dd, "Counter %s not enabled", entry->name); + return 0; + } + + hfi2_cdbg(CNTR, "cntr: %s vl %d psval 0x%llx", entry->name, vl, *psval); + + val = entry->rw_cntr(entry, context, vl, CNTR_MODE_R, 0); + + /* If its a synthetic counter there is more work we need to do */ + if (entry->flags & CNTR_SYNTH) { + if (sval == CNTR_MAX) { + /* No need to read already saturated */ + return CNTR_MAX; + } + + if (entry->flags & CNTR_32BIT) { + /* 32bit counters can wrap multiple times */ + u64 upper = sval >> 32; + u64 lower = (sval << 32) >> 32; + + if (lower > val) { /* hw wrapped */ + if (upper == CNTR_32BIT_MAX) + val = CNTR_MAX; + else + upper++; + } + + if (val != CNTR_MAX) + val = (upper << 32) | val; + + } else { + /* If we rolled we are saturated */ + if ((val < sval) || (val > CNTR_MAX)) + val = CNTR_MAX; + } + } + + *psval = val; + + hfi2_cdbg(CNTR, "\tNew val=0x%llx", val); + + return val; +} + +static u64 write_dev_port_cntr(struct hfi2_devdata *dd, + struct cntr_entry *entry, u64 *psval, + void *context, int vl, u64 data) +{ + u64 val; + + if (entry->flags & CNTR_DISABLED) { + dd_dev_err(dd, "Counter %s not enabled", entry->name); + return 0; + } + + hfi2_cdbg(CNTR, "cntr: %s vl %d psval 0x%llx", entry->name, vl, *psval); + + if (entry->flags & CNTR_SYNTH) { + *psval = data; + if (entry->flags & CNTR_32BIT) { + val = entry->rw_cntr(entry, context, vl, CNTR_MODE_W, + (data << 32) >> 32); + val = data; /* return the full 64bit value */ + } else { + val = entry->rw_cntr(entry, context, vl, CNTR_MODE_W, + data); + } + } else { + val = entry->rw_cntr(entry, context, vl, CNTR_MODE_W, data); + } + + *psval = val; + + hfi2_cdbg(CNTR, "\tNew val=0x%llx", val); + + return val; +} + +u64 hfi2_read_dev_cntr(struct hfi2_devdata *dd, int index, int vl) +{ + struct cntr_entry *entry; + u64 *sval; + + if (index < SHARED_DEV_CNTR_LAST) { + entry = &hfi2_shared_dev_cntrs[index]; + } else { + index -= dd->params->chip_dev_cntr_first; + if (index < 0 || index >= dd->params->chip_num_dev_cntrs) { + dd_dev_err(dd, "%s: invalid dev counter index 0x%x\n", + __func__, + index + dd->params->chip_dev_cntr_first); + return 0; + } + entry = &dd->params->chip_dev_cntrs[index]; + } + sval = dd->scntrs + entry->offset; + + if (vl != CNTR_INVALID_VL) + sval += vl; + + return read_dev_port_cntr(dd, entry, sval, dd, vl); +} + +u64 hfi2_write_dev_cntr(struct hfi2_devdata *dd, int index, int vl, u64 data) +{ + struct cntr_entry *entry; + u64 *sval; + + if (index < SHARED_DEV_CNTR_LAST) { + entry = &hfi2_shared_dev_cntrs[index]; + } else { + index -= dd->params->chip_dev_cntr_first; + if (index < 0 || index >= dd->params->chip_num_dev_cntrs) { + dd_dev_err(dd, "%s: invalid dev counter index 0x%x\n", + __func__, + index + dd->params->chip_dev_cntr_first); + return 0; + } + entry = &dd->params->chip_dev_cntrs[index]; + } + sval = dd->scntrs + entry->offset; + + if (vl != CNTR_INVALID_VL) + sval += vl; + + return write_dev_port_cntr(dd, entry, sval, dd, vl, data); +} + +/* return the counter entry for the given index, or NULL if invalid */ +static struct cntr_entry *get_port_entry(struct hfi2_devdata *dd, int index, + const char *caller) +{ + if (index < SHARED_PORT_CNTR_LAST) + return &hfi2_shared_port_cntrs[index]; + + index -= dd->params->chip_port_cntr_first; + if (index < 0 || index >= dd->params->chip_num_port_cntrs) { + dd_dev_err(dd, "%s: invalid port counter index 0x%x\n", caller, + index + dd->params->chip_port_cntr_first); + return NULL; + } + return &dd->params->chip_port_cntrs[index]; +} + +/* + * Counters may be indexed by vl or (indirectly) context number. + * + * Return the actual index for this counter, as determined by parameter + * in_index and counter type. + */ +static int get_port_entry_index(struct hfi2_devdata *dd, + struct cntr_entry *entry, int in_index) +{ + int out_index = 0; + + if (entry->flags & CNTR_OVF) { + if (in_index >= 0 && in_index < dd->num_rcd) { + /* vl is really a context # */ + out_index = dd->ovf_offset[in_index]; + } else { + dd_dev_err(dd, "bad ovl ctxt %d\n", in_index); + } + } else { + if (in_index != CNTR_INVALID_VL) + out_index = in_index; + } + return out_index; +} + +u64 hfi2_read_port_cntr(struct hfi2_pportdata *ppd, int index, int vl) +{ + struct hfi2_devdata *dd = ppd->dd; + struct cntr_entry *entry; + u64 *sval; + + entry = get_port_entry(dd, index, __func__); + if (!entry) + return 0; + sval = ppd->scntrs + entry->offset; + + sval += get_port_entry_index(dd, entry, vl); + + if (entry->flags & CNTR_DISABLED) { + /* skip disabled contexts */ + return 0; + } + + return read_dev_port_cntr(dd, entry, sval, ppd, vl); +} + +u64 hfi2_write_port_cntr(struct hfi2_pportdata *ppd, int index, int vl, + u64 data) +{ + struct hfi2_devdata *dd = ppd->dd; + struct cntr_entry *entry; + u64 *sval; + + entry = get_port_entry(dd, index, __func__); + if (!entry) + return 0; + sval = ppd->scntrs + entry->offset; + + sval += get_port_entry_index(dd, entry, vl); + + if (entry->flags & CNTR_DISABLED) { + /* skip disabled contexts */ + return 0; + } + + return write_dev_port_cntr(dd, entry, sval, ppd, vl, data); +} + +/* + * Perform a WFR specific check on whether to update synthetic counters. + */ +bool hfi2_wfr_check_synth_status(struct hfi2_devdata *dd) +{ + struct wfr_synth_data *sd = &dd->synth_data.wfr; + u64 cur_tx; + u64 cur_rx; + u64 total_flits; + bool update = false; + + /* + * Rather than keep beating on the CSRs pick a minimal set that we can + * check to watch for potential roll over. We can do this by looking at + * the number of flits sent/recv. If the total flits exceeds 32bits then + * we have to iterate all the counters and update. + */ + cur_rx = hfi2_read_dev_cntr(dd, C_DC_RCV_FLITS, CNTR_INVALID_VL); + cur_tx = hfi2_read_dev_cntr(dd, C_DC_XMIT_FLITS, CNTR_INVALID_VL); + + hfi2_cdbg(CNTR, + "[%d] curr tx=0x%llx rx=0x%llx :: last tx=0x%llx rx=0x%llx", + dd->unit, cur_tx, cur_rx, sd->last_tx, sd->last_rx); + + if ((cur_tx < sd->last_tx) || (cur_rx < sd->last_rx)) { + /* + * May not be strictly necessary to update but it won't hurt and + * simplifies the logic here. + */ + update = true; + hfi2_cdbg(CNTR, "[%d] Tripwire counter rolled, updating", + dd->unit); + } else { + total_flits = (cur_tx - sd->last_tx) + (cur_rx - sd->last_rx); + hfi2_cdbg(CNTR, "[%d] total flits 0x%llx limit 0x%llx", + dd->unit, total_flits, (u64)CNTR_32BIT_MAX); + if (total_flits >= CNTR_32BIT_MAX) { + hfi2_cdbg(CNTR, "[%d] 32bit limit hit, updating", + dd->unit); + update = true; + } + } + + return update; +} + +void hfi2_wfr_update_synth_status(struct hfi2_devdata *dd) +{ + struct wfr_synth_data *sd = &dd->synth_data.wfr; + + /* + * We want the value in the register. The goal is to keep track + * of the number of "ticks" not the counter value. In other + * words if the register rolls we want to notice it and go ahead + * and force an update. + */ + sd->last_tx = hfi2_read_dev_cntr(dd, C_DC_XMIT_FLITS, CNTR_INVALID_VL); + sd->last_rx = hfi2_read_dev_cntr(dd, C_DC_RCV_FLITS, CNTR_INVALID_VL); + + hfi2_cdbg(CNTR, "[%d] setting last tx/rx to 0x%llx 0x%llx", dd->unit, + sd->last_tx, sd->last_rx); +} + +static void do_update_synth_timer(struct work_struct *work) +{ + bool update; + int i; + struct hfi2_pportdata *ppd; + struct hfi2_devdata *dd = + container_of(work, struct hfi2_devdata, update_cntr_work); + + update = dd->params->check_synth_status(dd); + + if (update) { + hfi2_cdbg(CNTR, "[%d] Updating dd and ppd counters", dd->unit); + read_counters(dd, hfi2_shared_dev_cntrs, SHARED_DEV_CNTR_LAST, + dd, dd->scntrs); + read_counters(dd, dd->params->chip_dev_cntrs, + dd->params->chip_num_dev_cntrs, dd, dd->scntrs); + ppd = (struct hfi2_pportdata *)(dd + 1); + for (i = 0; i < dd->num_pports; i++, ppd++) { + read_counters(dd, hfi2_shared_port_cntrs, + SHARED_PORT_CNTR_LAST, ppd, ppd->scntrs); + read_counters(dd, dd->params->chip_port_cntrs, + dd->params->chip_num_port_cntrs, ppd, + ppd->scntrs); + } + dd->params->update_synth_status(dd); + } else { + hfi2_cdbg(CNTR, "[%d] No update necessary", dd->unit); + } +} + +static void update_synth_timer(struct timer_list *t) +{ + struct hfi2_devdata *dd = timer_container_of(dd, t, synth_stats_timer); + + queue_work(dd->update_cntr_wq, &dd->update_cntr_work); + mod_timer(&dd->synth_stats_timer, jiffies + HZ * SYNTH_CNT_TIME); +} + +static const char bit_type_32[] = ",32"; +static const int bit_type_32_sz = 3; +#define C_MAX_NAME 16 /* 15 chars + one for /0 */ + +/* + * Calculate and return the indexed name size. Keep in sync with + * copy_indexed_name(). + */ +static size_t size_indexed_name(const char *raw_name, int flags, int idx) +{ + char name[C_MAX_NAME]; + size_t sz; + + snprintf(name, C_MAX_NAME, "%s%d", raw_name, idx); + /* +1 for newline */ + sz = strlen(name) + 1; + /* add ",32" for 32-bit counters */ + if (flags & CNTR_32BIT) + sz += bit_type_32_sz; + + return sz; +} + +/* + * Copy indexed name into p. Advance and return p. Keep in sync with + * size_indexed_name(). + */ +static char *copy_indexed_name(char *p, const char *raw_name, int flags, + int idx) +{ + char name[C_MAX_NAME]; + + snprintf(name, C_MAX_NAME, "%s%d", raw_name, idx); + memcpy(p, name, strlen(name)); + p += strlen(name); + /* counter is 32 bits */ + if (flags & CNTR_32BIT) { + memcpy(p, bit_type_32, bit_type_32_sz); + p += bit_type_32_sz; + } + *p++ = '\n'; + + return p; +} + +/* + * Calculate and return the single name size. Keep in sync with + * copy_single_name(). + */ +static size_t size_single_name(const char *raw_name, int flags) +{ + size_t sz; + + /* +1 for newline */ + sz = strlen(raw_name) + 1; + /* add ",32" for 32-bit counters */ + if (flags & CNTR_32BIT) + sz += bit_type_32_sz; + + return sz; +} + +/* + * Copy single name into p. Advance and return p. Keep in sync with + * size_single_name(). + */ +static char *copy_single_name(char *p, const char *raw_name, int flags) +{ + memcpy(p, raw_name, strlen(raw_name)); + p += strlen(raw_name); + /* counter is 32 bits */ + if (flags & CNTR_32BIT) { + memcpy(p, bit_type_32, bit_type_32_sz); + p += bit_type_32_sz; + } + *p++ = '\n'; + + return p; +} + +/* + * Walk through the table, increasing the calculated number of counters and + * name size. + */ +static void size_cntr_names(struct hfi2_devdata *dd, struct cntr_entry *table, + int table_size, size_t *countp, size_t *szp) +{ + u32 sdma_engines = chip_sdma_engines(dd); + u32 num_rcv = chip_rcv_contexts(dd); + size_t count; + size_t sz; + int i, j; + u8 ovf_offset; + + count = *countp; /* need current count */ + sz = 0; + ovf_offset = 0; + for (i = 0; i < table_size; i++) { + if (table[i].flags & CNTR_DISABLED) { + hfi2_dbg_early("\tSkipping %s\n", table[i].name); + continue; + } + + table[i].offset = count; + if (table[i].flags & CNTR_VL) { + for (j = 0; j < C_VL_COUNT; j++) { + sz += size_indexed_name(table[i].name, + table[i].flags, + vl_from_idx(j)); + count++; + } + } else if (table[i].flags & CNTR_SDMA) { + for (j = 0; j < sdma_engines; j++) { + sz += size_indexed_name(table[i].name, + table[i].flags, j); + count++; + } + } else if (table[i].flags & CNTR_OVF) { + for (j = 0; j < num_rcv; j++) { + if (test_bit(j, dd->ovf_disabled)) + continue; + dd->ovf_offset[j] = ovf_offset; + sz += size_indexed_name(table[i].name, + table[i].flags, j); + count++; + ovf_offset++; + } + } else { + sz += size_single_name(table[i].name, table[i].flags); + count++; + } + } + + /* return updated sizes */ + *countp = count; + *szp += sz; +} + +/* + * Fill the counter names into p. Return p's final value. + */ +static char *fill_cntr_names(struct hfi2_devdata *dd, struct cntr_entry *table, + int table_size, char *p) +{ + u32 sdma_engines = chip_sdma_engines(dd); + u32 num_rcv = chip_rcv_contexts(dd); + int i, j; + + for (i = 0; i < table_size; i++) { + if (table[i].flags & CNTR_DISABLED) { + /* Nothing */ + } else if (table[i].flags & CNTR_VL) { + for (j = 0; j < C_VL_COUNT; j++) { + p = copy_indexed_name(p, table[i].name, + table[i].flags, + vl_from_idx(j)); + } + } else if (table[i].flags & CNTR_SDMA) { + for (j = 0; j < sdma_engines; j++) { + p = copy_indexed_name(p, table[i].name, + table[i].flags, j); + } + } else if (table[i].flags & CNTR_OVF) { + for (j = 0; j < num_rcv; j++) { + if (test_bit(j, dd->ovf_disabled)) + continue; + p = copy_indexed_name(p, table[i].name, + table[i].flags, j); + } + } else { + p = copy_single_name(p, table[i].name, table[i].flags); + } + } + + return p; +} + +static int init_cntrs(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + int i, j; + size_t sz; + struct hfi2_pportdata *ppd; + char *p; + + /* set up the stats timers; the add_timer calls are done at the end */ + timer_setup(&dd->synth_stats_timer, update_synth_timer, 0); + timer_setup(&dd->rcverr_timer, update_rcverr_timer, 0); + /* Assume the hardware counter has been reset */ + for (i = 0; i < dd->num_pports; i++) + dd->pport[i].rcv_ovfl_cnt = 0; + + /***********************/ + /* per device counters */ + /***********************/ + + /* size names and determine how many we have */ + dd->ndevcntrs = 0; + sz = 0; + size_cntr_names(dd, hfi2_shared_dev_cntrs, SHARED_DEV_CNTR_LAST, + &dd->ndevcntrs, &sz); + size_cntr_names(dd, dd->params->chip_dev_cntrs, + dd->params->chip_num_dev_cntrs, &dd->ndevcntrs, &sz); + + /* allocate space for the counter values */ + dd->cntrs = kcalloc(dd->ndevcntrs + hfi2_num_driver_cntrs, sizeof(u64), + GFP_KERNEL); + if (!dd->cntrs) + goto bail; + + dd->scntrs = kcalloc(dd->ndevcntrs, sizeof(u64), GFP_KERNEL); + if (!dd->scntrs) + goto bail; + + /* allocate space for the counter names */ + dd->cntrnameslen = sz; + dd->cntrnames = kmalloc(sz, GFP_KERNEL); + if (!dd->cntrnames) + goto bail; + + /* fill in the names */ + p = fill_cntr_names(dd, hfi2_shared_dev_cntrs, SHARED_DEV_CNTR_LAST, + dd->cntrnames); + fill_cntr_names(dd, dd->params->chip_dev_cntrs, + dd->params->chip_num_dev_cntrs, p); + + /*********************/ + /* per port counters */ + /*********************/ + + /* + * Go through the counters for the overflows and disable the ones we + * don't need. This varies based on platform so we need to do it + * dynamically here. + */ + bitmap_fill(dd->ovf_disabled, MAX_CTXTS); + for (i = 0; i < dd->num_pports; i++) { + struct hfi2_portrsrcs *pr = &dr->ppr[i]; + + for (j = 0; j < pr->num_rcv_contexts; j++) { + u16 ctxt = pr->rcv_context_base + j; + + clear_bit(ctxt, dd->ovf_disabled); + } + } + + /* size port counter names and determine how many we have */ + dd->nportcntrs = 0; + sz = 0; + size_cntr_names(dd, hfi2_shared_port_cntrs, SHARED_PORT_CNTR_LAST, + &dd->nportcntrs, &sz); + size_cntr_names(dd, dd->params->chip_port_cntrs, + dd->params->chip_num_port_cntrs, &dd->nportcntrs, &sz); + + /* allocate space for the counter names */ + dd->portcntrnameslen = sz; + dd->portcntrnames = kmalloc(sz, GFP_KERNEL); + if (!dd->portcntrnames) + goto bail; + + /* fill in port cntr names */ + p = fill_cntr_names(dd, hfi2_shared_port_cntrs, SHARED_PORT_CNTR_LAST, + dd->portcntrnames); + fill_cntr_names(dd, dd->params->chip_port_cntrs, + dd->params->chip_num_port_cntrs, p); + + /* allocate per port storage for counter values */ + ppd = (struct hfi2_pportdata *)(dd + 1); + for (i = 0; i < dd->num_pports; i++, ppd++) { + ppd->cntrs = kcalloc(dd->nportcntrs, sizeof(u64), GFP_KERNEL); + if (!ppd->cntrs) + goto bail; + + ppd->scntrs = kcalloc(dd->nportcntrs, sizeof(u64), GFP_KERNEL); + if (!ppd->scntrs) + goto bail; + } + + /* CPU counters need to be allocated and zeroed */ + if (init_cpu_counters(dd)) + goto bail; + + dd->update_cntr_wq = alloc_ordered_workqueue("hfi2_update_cntr_%d", + WQ_MEM_RECLAIM, dd->unit); + if (!dd->update_cntr_wq) + goto bail; + + INIT_WORK(&dd->update_cntr_work, do_update_synth_timer); + INIT_WORK(&dd->rcverr_work, do_rcverr_timer); + + mod_timer(&dd->synth_stats_timer, jiffies + HZ * SYNTH_CNT_TIME); + mod_timer(&dd->rcverr_timer, jiffies + HZ * RCVERR_CHECK_TIME); + return 0; +bail: + free_cntrs(dd); + return -ENOMEM; +} + +static u32 chip_to_opa_lstate(struct hfi2_devdata *dd, u32 chip_lstate) +{ + switch (chip_lstate) { + case LSTATE_DOWN: + return IB_PORT_DOWN; + case LSTATE_INIT: + return IB_PORT_INIT; + case LSTATE_ARMED: + return IB_PORT_ARMED; + case LSTATE_ACTIVE: + return IB_PORT_ACTIVE; + default: + dd_dev_err( + dd, + "Unknown logical state 0x%x, reporting IB_PORT_DOWN\n", + chip_lstate); + return IB_PORT_DOWN; + } +} + +u32 hfi2_chip_to_opa_pstate(struct hfi2_devdata *dd, u32 chip_pstate) +{ + /* look at the HFI meta-states only */ + switch (chip_pstate & 0xf0) { + case PLS_DISABLED: + return IB_PORTPHYSSTATE_DISABLED; + case PLS_OFFLINE: + return OPA_PORTPHYSSTATE_OFFLINE; + case PLS_POLLING: + return IB_PORTPHYSSTATE_POLLING; + case PLS_CONFIGPHY: + return IB_PORTPHYSSTATE_TRAINING; + case PLS_LINKUP: + return IB_PORTPHYSSTATE_LINKUP; + case PLS_PHYTEST: + return IB_PORTPHYSSTATE_PHY_TEST; + default: + dd_dev_err(dd, "Unexpected chip physical state of 0x%x\n", + chip_pstate); + return IB_PORTPHYSSTATE_DISABLED; + } +} + +/* return the OPA port physical state name */ +const char *hfi2_opa_pstate_name(u32 pstate) +{ + static const char *const port_physical_names[] = { + "PHYS_NOP", + "reserved1", + "PHYS_POLL", + "PHYS_DISABLED", + "PHYS_TRAINING", + "PHYS_LINKUP", + "PHYS_LINK_ERR_RECOVER", + "PHYS_PHY_TEST", + "reserved8", + "PHYS_OFFLINE", + "PHYS_GANGED", + "PHYS_TEST", + }; + if (pstate < ARRAY_SIZE(port_physical_names)) + return port_physical_names[pstate]; + return "unknown"; +} + +/** + * hfi2_update_statusp - Update userspace status flag + * @ppd: Port data structure + * @state: port state information + * + * Actual port status is determined by the host_link_state value + * in the ppd. + * + * host_link_state MUST be updated before updating the user space + * statusp. + */ +void hfi2_update_statusp(struct hfi2_pportdata *ppd, u32 state) +{ + /* + * Set port status flags in the page mapped into userspace + * memory. Do it here to ensure a reliable state - this is + * the only function called by all state handling code. + * Always set the flags due to the fact that the cache value + * might have been changed explicitly outside of this + * function. + */ + if (ppd->statusp) { + switch (state) { + case IB_PORT_DOWN: + case IB_PORT_INIT: + *ppd->statusp &= + ~(HFI2_STATUS_IB_CONF | HFI2_STATUS_IB_READY); + break; + case IB_PORT_ARMED: + *ppd->statusp |= HFI2_STATUS_IB_CONF; + break; + case IB_PORT_ACTIVE: + *ppd->statusp |= HFI2_STATUS_IB_READY; + break; + } + } + ppd_dev_info(ppd, "logical state changed to %s (0x%x)\n", + ib_port_state_to_str(state), state); +} + +/** + * wait_logical_linkstate - wait for an IB link state change to occur + * @ppd: port device + * @state: the state to wait for + * @msecs: the number of milliseconds to wait + * + * Wait up to msecs milliseconds for IB link state change to occur. + * For now, take the easy polling route. + * Returns 0 if state reached, otherwise -ETIMEDOUT. + */ +static int wait_logical_linkstate(struct hfi2_pportdata *ppd, u32 state, + int msecs) +{ + unsigned long timeout; + u32 new_state; + + timeout = jiffies + msecs_to_jiffies(msecs); + while (1) { + new_state = chip_to_opa_lstate( + ppd->dd, hfi2_read_logical_state(ppd->dd)); + if (new_state == state) + break; + if (time_after(jiffies, timeout)) { + ppd_dev_err(ppd, + "timeout waiting for link state 0x%x\n", + state); + return -ETIMEDOUT; + } + msleep(20); + } + + return 0; +} + +void hfi2_log_state_transition(struct hfi2_pportdata *ppd, u32 state) +{ + u32 ib_pstate = hfi2_chip_to_opa_pstate(ppd->dd, state); + + ppd_dev_info(ppd, "physical state changed to %s (0x%x), phy 0x%x\n", + hfi2_opa_pstate_name(ib_pstate), ib_pstate, state); +} + +/* + * Read the physical hardware link state and check if it matches host + * drivers anticipated state. + */ +static void log_physical_state(struct hfi2_pportdata *ppd, u32 state) +{ + u32 read_state = hfi2_read_physical_state(ppd->dd); + + if (read_state == state) { + hfi2_log_state_transition(ppd, state); + } else { + ppd_dev_err(ppd, "anticipated phy link state 0x%x, read 0x%x\n", + state, read_state); + } +} + +/* + * wait_physical_linkstate - wait for an physical link state change to occur + * @ppd: port device + * @state: the state to wait for + * @msecs: the number of milliseconds to wait + * + * Wait up to msecs milliseconds for physical link state change to occur. + * Returns 0 if state reached, otherwise -ETIMEDOUT. + */ +static int wait_physical_linkstate(struct hfi2_pportdata *ppd, u32 state, + int msecs) +{ + u32 read_state; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(msecs); + while (1) { + read_state = hfi2_read_physical_state(ppd->dd); + if (read_state == state) + break; + if (time_after(jiffies, timeout)) { + ppd_dev_err(ppd, + "timeout waiting for phy link state 0x%x\n", + state); + return -ETIMEDOUT; + } + usleep_range(1950, 2050); /* sleep 2ms-ish */ + } + + hfi2_log_state_transition(ppd, state); + return 0; +} + +/* + * wait_phys_link_offline_quiet_substates - wait for any offline substate + * @ppd: port device + * @msecs: the number of milliseconds to wait + * + * Wait up to msecs milliseconds for any offline physical link + * state change to occur. + * Returns 0 if at least one state is reached, otherwise -ETIMEDOUT. + */ +static int wait_phys_link_offline_substates(struct hfi2_pportdata *ppd, + int msecs) +{ + u32 read_state; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(msecs); + while (1) { + read_state = hfi2_read_physical_state(ppd->dd); + if ((read_state & 0xF0) == PLS_OFFLINE) + break; + if (time_after(jiffies, timeout)) { + ppd_dev_err( + ppd, + "timeout waiting for phy link offline.quiet substates. Read state 0x%x, %dms\n", + read_state, msecs); + return -ETIMEDOUT; + } + usleep_range(1950, 2050); /* sleep 2ms-ish */ + } + + hfi2_log_state_transition(ppd, read_state); + return read_state; +} + +/* + * wait_phys_link_out_of_offline - wait for any out of offline state + * @ppd: port device + * @msecs: the number of milliseconds to wait + * + * Wait up to msecs milliseconds for any out of offline physical link + * state change to occur. + * Returns 0 if at least one state is reached, otherwise -ETIMEDOUT. + */ +static int wait_phys_link_out_of_offline(struct hfi2_pportdata *ppd, int msecs) +{ + u32 read_state; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(msecs); + while (1) { + read_state = hfi2_read_physical_state(ppd->dd); + if ((read_state & 0xF0) != PLS_OFFLINE) + break; + if (time_after(jiffies, timeout)) { + ppd_dev_err( + ppd, + "timeout waiting for phy link out of offline. Read state 0x%x, %dms\n", + read_state, msecs); + return -ETIMEDOUT; + } + usleep_range(1950, 2050); /* sleep 2ms-ish */ + } + + hfi2_log_state_transition(ppd, read_state); + return read_state; +} + +void hfi2_init_ctxt(struct send_context *sc) +{ + if (sc) { + hfi2_priv_reg_op(sc->dd, sc->ppd->hw_pidx, sc->hw_context, + sc->type, SC_CHK_INIT_OP, 0); + } +} + +int hfi2_tempsense_rd(struct hfi2_devdata *dd, struct hfi2_temp *temp) +{ + int ret = 0; + u64 reg; + + if (dd->icode != ICODE_RTL_SILICON) { + if (HFI2_CAP_IS_KSET(PRINT_UNIMPL)) + dd_dev_info(dd, "%s: tempsense not supported by HW\n", + __func__); + return -EINVAL; + } + + if (dd->params->chip_type != CHIP_WFR) + return -EINVAL; + + reg = hfi2_read_csr(dd, ASIC_STS_THERM); + temp->curr = ((reg >> ASIC_STS_THERM_CURR_TEMP_SHIFT) & + ASIC_STS_THERM_CURR_TEMP_MASK); + temp->lo_lim = ((reg >> ASIC_STS_THERM_LO_TEMP_SHIFT) & + ASIC_STS_THERM_LO_TEMP_MASK); + temp->hi_lim = ((reg >> ASIC_STS_THERM_HI_TEMP_SHIFT) & + ASIC_STS_THERM_HI_TEMP_MASK); + temp->crit_lim = ((reg >> ASIC_STS_THERM_CRIT_TEMP_SHIFT) & + ASIC_STS_THERM_CRIT_TEMP_MASK); + /* triggers is a 3-bit value - 1 bit per trigger. */ + temp->triggers = (u8)((reg >> ASIC_STS_THERM_LOW_SHIFT) & 0x7); + + return ret; +} + +/* ========================================================================= */ + +/** + * read_mod_write() - Calculate the IRQ register index and set/clear the bits + * @dd: valid devdata + * @src: IRQ source to determine register index from + * @bits: the bits to set or clear + * @set: true == set the bits, false == clear the bits + * + */ +static void read_mod_write(struct hfi2_devdata *dd, u16 src, u64 bits, bool set) +{ + u64 reg; + u16 idx = src / BITS_PER_REGISTER; + u32 cce_int_mask_reg = dd->params->cce_int_mask_reg + (8 * idx); + unsigned long flags; + + spin_lock_irqsave(&dd->irq_src_lock, flags); + reg = hfi2_read_csr(dd, cce_int_mask_reg); + if (set) { + reg |= bits; + dd->gi_mask[idx].cce_int_mask |= bits; + } else { + reg &= ~bits; + dd->gi_mask[idx].cce_int_mask &= ~bits; + } + hfi2_write_csr(dd, cce_int_mask_reg, reg); + spin_unlock_irqrestore(&dd->irq_src_lock, flags); +} + +/** + * hfi2_set_intr_bits() - Enable/disable a range (one or more) IRQ sources + * @dd: valid devdata + * @first: first IRQ source to set/clear + * @last: last IRQ source (inclusive) to set/clear + * @set: true == set the bits, false == clear the bits + * + * If first == last, set the exact source. + */ +int hfi2_set_intr_bits(struct hfi2_devdata *dd, u16 first, u16 last, bool set) +{ + u64 bits = 0; + u64 bit; + u16 src; + + if (last > dd->params->is_last_source) + return -EINVAL; + if (last < first) + return -ERANGE; + + for (src = first; src <= last; src++) { + bit = src % BITS_PER_REGISTER; + /* wrapped to next register? */ + if (!bit && bits) { + read_mod_write(dd, src - 1, bits, set); + bits = 0; + } + bits |= BIT_ULL(bit); + } + read_mod_write(dd, last, bits, set); + + return 0; +} + +/* + * Clear all interrupt sources on the chip. + */ +void hfi2_clear_all_interrupts(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + int i; + u32 cce_int_clear_reg = dd->params->cce_int_clear_reg; + + for (i = 0; i < dd->params->num_int_csrs; i++) + hfi2_write_csr(dd, cce_int_clear_reg + (8 * i), ~(u64)0); + + hfi2_write_csr(dd, dd->params->csr_err_clear_reg, ~(u64)0); + hfi2_write_csr(dd, dd->params->send_pio_err_clear_reg, ~(u64)0); + hfi2_write_csr(dd, dd->params->send_dma_err_clear_reg, ~(u64)0); + for (i = dr->c.first_send_context; i < dr->c.last_send_context; i++) + write_sctxt_csr(dd, i, dd->params->send_ctxt_err_clear_reg, + ~(u64)0); + for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; i++) + write_sdma_csr(dd, i, dd->params->send_dma_eng_err_clear_reg, + ~(u64)0); + + if (dd->params->chip_type == CHIP_WFR) { + /* CPORT is initializing these */ + hfi2_write_csr(dd, CCE_ERR_CLEAR, ~(u64)0); + for (i = 0; i < dd->num_pports; i++) { + write_iport_csr(dd, i, dd->params->rcv_err_clear_reg, + ~(u64)0); + write_eport_csr(dd, i, + dd->params->send_egress_err_clear_reg, + ~(u64)0); + } + + /* only WFR has these blocks */ + hfi2_write_csr(dd, MISC_ERR_CLEAR, ~(u64)0); + hfi2_write_csr(dd, DCC_ERR_FLG_CLR, ~(u64)0); + hfi2_write_csr(dd, DC_LCB_ERR_CLR, ~(u64)0); + hfi2_write_csr(dd, DC_DC8051_ERR_CLR, ~(u64)0); + } +} + +/* + * Remap the interrupt source from the general handler to the given MSI-X + * interrupt. + */ +void hfi2_remap_intr(struct hfi2_devdata *dd, int isrc, int msix_intr) +{ + u64 reg; + int m, n; + + /* clear from the handled mask of the general interrupt */ + m = isrc / 64; + n = isrc % 64; + if (likely(m < dd->params->num_int_csrs)) { + dd->gi_mask[m].remap &= ~((u64)1 << n); + } else { + dd_dev_err(dd, "remap interrupt err\n"); + return; + } + + /* direct the chip source to the given MSI-X interrupt */ + m = isrc / 8; + n = isrc % 8; + reg = hfi2_read_csr(dd, dd->params->cce_msix_int_map_vec_reg + (8 * m)); + reg &= ~((u64)0xff << (8 * n)); + reg |= ((u64)msix_intr & 0xff) << (8 * n); + hfi2_write_csr(dd, dd->params->cce_msix_int_map_vec_reg + (8 * m), reg); +} + +void hfi2_remap_sdma_interrupts(struct hfi2_devdata *dd, int engine, + int msix_intr) +{ + /* + * SDMA engine interrupt sources grouped by type, rather than + * engine. Per-engine interrupts are as follows: + * SDMA + * SDMAProgress + * SDMAIdle + */ + hfi2_remap_intr(dd, dd->params->is_sdma_start + engine, msix_intr); + hfi2_remap_intr(dd, dd->params->is_sdma_progress_start + engine, + msix_intr); + hfi2_remap_intr(dd, dd->params->is_sdma_idle_start + engine, msix_intr); +} + +/* + * Set the general handler to accept all interrupts, remap all + * chip interrupts back to MSI-X 0. + */ +void hfi2_reset_interrupts(struct hfi2_devdata *dd) +{ + int i; + u32 cce_int_mask_reg = dd->params->cce_int_mask_reg; + + /* all interrupts handled by the general handler */ + for (i = 0; i < dd->params->num_int_csrs; i++) { + dd->gi_mask[i].remap = ~(u64)0; + dd->gi_mask[i].cce_int_mask = + hfi2_read_csr(dd, cce_int_mask_reg + (8 * i)); + } + + /* all chip interrupts map to MSI-X 0 */ + for (i = 0; i < dd->params->num_int_map_csrs; i++) + hfi2_write_csr( + dd, dd->params->cce_msix_int_map_vec_reg + (8 * i), 0); +} + +/** + * set_up_interrupts() - Initialize the IRQ resources and state + * @dd: valid devdata + * + */ +static int set_up_interrupts(struct hfi2_devdata *dd) +{ + int ret; + + /* mask all interrupts */ + hfi2_set_intr_bits(dd, 0, dd->params->is_last_source, false); + + /* clear all pending interrupts */ + hfi2_clear_all_interrupts(dd); + + /* reset general handler mask, chip MSI-X mappings */ + hfi2_reset_interrupts(dd); + + /* ask for MSI-X interrupts */ + ret = hfi2_msix_initialize(dd); + if (ret) + return ret; + + ret = hfi2_msix_early_request_irqs(dd); + if (ret) + hfi2_msix_clean_up_interrupts(dd); + + return ret; +} + +static int late_set_up_interrupts(struct hfi2_devdata *dd) +{ + int ret; + + ret = hfi2_msix_request_irqs(dd); + if (ret) + hfi2_msix_clean_up_interrupts(dd); + + return ret; +} + +/* + * Reduce the total per-port user receive context counts by the given amount. + * + * Return 0 if success, -EINVAL if there is not enough extra. + */ +static int reduce_ctxts(struct hfi2_devdata *dd, u32 *counts, int amount) +{ + const u32 count_min = 0; /* do not allow count to go below this value */ + int pidx; + + /* remove one at a time, round robin */ + while (amount > 0) { + bool adjusted = false; + + for (pidx = 0; pidx < dd->num_pports && amount > 0; pidx++) { + if (counts[pidx] > count_min) { + counts[pidx]--; + amount--; + adjusted = true; + } + } + + if (!adjusted) + return -EINVAL; + } + + return 0; +} + +static int reduce_rcv_ctxts(struct hfi2_devdata *dd, u32 *counts, int amount, + const char *why) +{ + int ret = reduce_ctxts(dd, counts, amount); + + if (ret) { + dd_dev_err( + dd, + "Cannot reduce user receive contexts requested by %d [%s]\n", + amount, why); + } + return ret; +} + +/* return true if the fabric is reachable on the card */ +/* this function does not range validate pidx */ +static bool hardware_pidx_available(struct hfi2_devdata *dd, int pidx) +{ + /* only need to check JKR */ + if (dd->params->chip_type != CHIP_JKR) + return true; + + /* dual port JKR has all ports available */ + if (dd->pcidev->subsystem_device == PCI_SUBDEVICE_CN5000_DUAL_PORT || + dd->pcidev->subsystem_device == PCI_SUBDEVICE_CN5000_DUAL_PORT_PS) + return true; + + /* port swapped single port JKR only uses the first port */ + if (dd->pcidev->subsystem_device == + PCI_SUBDEVICE_CN5000_SINGLE_PORT_PS && + pidx == 0) + return true; + + /* single port JKR only uses the second port */ + if (dd->pcidev->subsystem_device == PCI_DEVICE_ID_CORNELIS_CN5000 && + pidx == 1) + return true; + + return false; +} + +/* + * Decide how to divide resources between ports. Resources include + * receive contexts, RSM table, RcvArray, and send contexts. + * + * User context resources may be asymmetric across ports. + * + * Receive contexts allocated in order at each port base: + * Control context + * Kernel contexts + * Dynamic context pool (user and netdev) + * + * These fields are set: + * + * dd->rsrcs.ppr[*]: + * num_rcv_contexts - number of contexts being used for this port + * n_krcv_queues - number of kernel contexts for each port + * (includes control context) + * num_netdev_contexts - number of reserved netdev contexts for each port + * num_user_conexts - number of user contexts for this port + * rcv_context_base - first context for this port + * first_dyn_alloc_ctxt - first dynamically allocated (user) context for + * this port + * dd: + * rcv_entries - details on RcvArray entries for each port + * num_send_contexts - number of PIO send contexts being used + * ppd: + * freectxts - number of free user contexts for this port + * rcv_array_base - first RcvArray entry for this port + */ +static int set_up_context_variables(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + u32 num_kernel_contexts[LARGEST_NUM_PORTS]; + u32 num_netdev_contexts[LARGEST_NUM_PORTS]; + u32 def_kernel_contexts; + u32 def_netdev_contexts; + u32 num_usr_ctxts; + u32 num_netdev; + u32 num_kctxts; + int ret; + int pidx; + int base; + int rmt_count; + int rcv_pool_count; + int total_netdev; + int rcvarray_avail; + int max_eager_allowed; + int total_groups; + int over; + char *limited; + u32 total_rcv; + u32 n_usr_ctxts[LARGEST_NUM_PORTS]; + u32 send_contexts = dr->c.last_send_context - dr->c.first_send_context; + u32 rcv_contexts = dr->c.last_rcv_context - dr->c.first_rcv_context; + bool recalculated = false; + + /* + * Calculate the default number of per-port kernel receive contexts. + * + * hfi2_n_krcvqs is the sum of module parameter kernel receive contexts, + * hfi2_krcvqs[]. It does not include the control context, so add that. + */ + if (hfi2_n_krcvqs) + def_kernel_contexts = hfi2_n_krcvqs + 1; + else + def_kernel_contexts = DEFAULT_KRCVQS + 1; + + def_netdev_contexts = hfi2_num_netdev_contexts( + dd, HFI2_MAX_NETDEV_CTXTS, cpu_online_mask); + + /* obtain requested user context numbers from module parameters */ + num_usr_ctxts = 0; + num_netdev = 0; + num_kctxts = 0; + for (pidx = 0; pidx < dd->num_pports; pidx++) { + int count = hfi2_get_num_user_contexts(dd, pidx); + /* + * Per-port user contexts defaults to negative if unset in + * the module parameter. + * - unavailable ports always have zero user contexts no + * matter what the parameter says + * - default to 1 user context per real (non-HT) CPU core + */ + if (!hardware_pidx_available(dd, pidx)) + count = 0; + if (count < 0) + count = num_online_cpus(); + n_usr_ctxts[pidx] = count; + + /* no user contexts implies no port */ + if (count == 0) { + num_kernel_contexts[pidx] = 0; + num_netdev_contexts[pidx] = 0; + } else { + num_kernel_contexts[pidx] = def_kernel_contexts; + num_netdev_contexts[pidx] = def_netdev_contexts; + num_netdev += def_netdev_contexts; + num_kctxts += def_kernel_contexts; + } + num_usr_ctxts += n_usr_ctxts[pidx]; + } + if (rcv_contexts < num_kctxts + num_usr_ctxts + num_netdev) { + dd_dev_warn(dd, "Disabling netdev on small configuration\n"); + for (pidx = 0; pidx < dd->num_pports; pidx++) + num_netdev_contexts[pidx] = 0; + } + +do_recalc: + /* + * Adjust the counts given a global max. + */ + total_rcv = 0; + for (pidx = 0; pidx < dd->num_pports; pidx++) { + total_rcv += num_kernel_contexts[pidx] + + num_netdev_contexts[pidx] + n_usr_ctxts[pidx]; + } + + if (rcv_contexts < total_rcv) { + over = total_rcv - rcv_contexts; + ret = reduce_rcv_ctxts(dd, n_usr_ctxts, over, + "available receive contexts"); + if (ret) + return -EINVAL; + /* total_rcv is no longer valid */ + } + + /* + * RMT entries are allocated as follows: + * 1. QOS (0 to 128 entries) + * 2. FECN (num_kernel_context - 1 [a] + num_user_contexts + + * num_netdev_contexts [b]) + * 3. netdev (NUM_NETDEV_MAP_ENTRIES) + * + * Notes: + * [a] Kernel contexts (except control) are included in FECN if kernel + * TID_RDMA is active. + * [b] Netdev and user contexts are randomly allocated from the same + * context pool, so FECN must cover all contexts in the pool. + */ + rmt_count = 0; + for (pidx = 0; pidx < dd->num_pports; pidx++) { + /* no RMT used if port is not available */ + if (num_kernel_contexts[pidx] == 0) + continue; + rmt_count += (HFI2_CAP_IS_KSET(TID_RDMA) ? + (num_kernel_contexts[pidx] - 1) : + 0) + + n_usr_ctxts[pidx] + num_netdev_contexts[pidx] + + NUM_NETDEV_MAP_ENTRIES + + qos_rmt_entries(num_kernel_contexts[pidx] - 1, + NULL, NULL); + } + + if (rmt_count > dd->params->rsm_map_table_entries) { + over = rmt_count - dd->params->rsm_map_table_entries; + ret = reduce_rcv_ctxts(dd, n_usr_ctxts, over, + "available RMT entries"); + if (ret) + return -EINVAL; + } + + /* + * For each port, the first N are kernel contexts, the rest are + * user/netdev contexts + */ + + dd_dev_info(dd, "rcv contexts: avail %d\n", rcv_contexts); + base = dr->c.first_rcv_context; + total_rcv = 0; /* recalculate */ + for (pidx = 0; pidx < dd->num_pports; pidx++) { + struct hfi2_pportdata *ppd = &dd->pport[pidx]; + struct hfi2_portrsrcs *pr = &dr->ppr[pidx]; + + pr->n_krcv_queues = num_kernel_contexts[pidx]; + pr->num_netdev_contexts = num_netdev_contexts[pidx]; + pr->num_rcv_contexts = num_kernel_contexts[pidx] + + num_netdev_contexts[pidx] + + n_usr_ctxts[pidx]; + pr->num_user_contexts = n_usr_ctxts[pidx]; + pr->rcv_context_base = base; + ppd->freectxts = pr->num_user_contexts; + pr->first_dyn_alloc_ctxt = + pr->rcv_context_base + num_kernel_contexts[pidx]; + ppd_dev_info( + ppd, + " rcv ctxts: base %d, used %d (kernel %d, netdev %u, user %u)\n", + pr->rcv_context_base, pr->num_rcv_contexts, + pr->n_krcv_queues, pr->num_netdev_contexts, + pr->num_user_contexts); + + base += pr->num_rcv_contexts; + total_rcv += pr->num_rcv_contexts; + } + + /* + * Receive array allocation: + * Avoid first N RcvArray entries. + * + * All RcvArray entries are divided into groups of 8. This + * is required by the hardware and will speed up writes to + * consecutive entries by using write-combining of the entire + * cacheline. + * + * The number of groups are evenly divided among all contexts. + */ + dd->rcv_entries.group_size = RCV_INCREMENT; + rcvarray_avail = dr->c.last_rcvarray_entry - dr->c.first_rcvarray_entry; + total_groups = rcvarray_avail / dd->rcv_entries.group_size; + if (total_rcv) + dd->rcv_entries.ngroups = total_groups / total_rcv; + else + dd->rcv_entries.ngroups = 0; /* alternate: total_groups */ + max_eager_allowed = dd->params->max_eager_entries * 2; + if (dd->rcv_entries.ngroups * dd->rcv_entries.group_size > + max_eager_allowed) { + dd->rcv_entries.ngroups = + max_eager_allowed / dd->rcv_entries.group_size; + limited = " (limited by max eager entries)"; + } else { + limited = ""; + } + dd_dev_info(dd, "RcvArray per-context groups %u%s, unused groups %u\n", + dd->rcv_entries.ngroups, limited, + total_groups - (dd->rcv_entries.ngroups * total_rcv)); + + base = dr->c.first_rcvarray_entry; + for (pidx = 0; pidx < dd->num_pports; pidx++) { + struct hfi2_portrsrcs *pr = &dr->ppr[pidx]; + + pr->rcv_array_base = base; + base += pr->num_rcv_contexts * + (dd->rcv_entries.ngroups * dd->rcv_entries.group_size); + } + + /* + * PIO send contexts + */ + ret = hfi2_init_sc_pools_and_sizes(dd); + if (ret < 0) + return ret; + dd->num_send_contexts = ret; + + dd_dev_info( + dd, + "send contexts: avail %d, used %d (kernel %d, ack %d, user %d, vl15 %d)\n", + send_contexts, dd->num_send_contexts, + dd->sc_sizes[SC_KERNEL].count, dd->sc_sizes[SC_ACK].count, + dd->sc_sizes[SC_USER].count, dd->sc_sizes[SC_VL15].count); + + /* + * There may be less PIO user send contexts available than user + * receive contexts. If so, reduce the requested user receive + * context count and go back to re-calculate the resources. + */ + rcv_pool_count = 0; + total_netdev = 0; + for (pidx = 0; pidx < dd->num_pports; pidx++) { + struct hfi2_portrsrcs *pr = &dr->ppr[pidx]; + + rcv_pool_count += + pr->num_netdev_contexts + pr->num_user_contexts; + total_netdev += pr->num_netdev_contexts; + } + + if (rcv_pool_count > dd->sc_sizes[SC_USER].count) { + const char *action = recalculated ? "fail" : "recalculating"; + + dd_dev_info(dd, "too many user rc %d vs sc %d - %s", + rcv_pool_count, dd->sc_sizes[SC_USER].count, + action); + if (recalculated) + return -EINVAL; + + /* netdev is required, enforce that many in the pool */ + if (dd->sc_sizes[SC_USER].count < total_netdev) { + dd_dev_err( + dd, + "more pool rcv contexts required than available\n"); + return -EINVAL; + } + + over = rcv_pool_count - dd->sc_sizes[SC_USER].count; + ret = reduce_rcv_ctxts(dd, n_usr_ctxts, over, + "available send contexts"); + if (ret) + return -EINVAL; + dd_dev_info(dd, "reducing requested receive contexts by %d", + over); + recalculated = true; + goto do_recalc; + } + + return 0; +} + +/* + * Set the device/port partition key table. The MAD code + * will ensure that, at least, the partial management + * partition key is present in the table. + */ +static void set_partition_keys(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg = 0; + int i; + + /* cport controls setting the hardware pkey table on later hardware */ + if (dd->params->chip_type != CHIP_WFR) + return; + + ppd_dev_info(ppd, "Setting partition keys\n"); + for (i = 0; i < hfi2_get_npkeys(dd); i++) { + reg |= (ppd->pkeys[i] & RCV_PARTITION_KEY_PARTITION_KEY_A_MASK) + << ((i % 4) * RCV_PARTITION_KEY_PARTITION_KEY_B_SHIFT); + /* Each register holds 4 PKey values. */ + if ((i % 4) == 3) { + write_iport_csr(dd, ppd->hw_pidx, + dd->params->rcv_partition_key_reg + + ((i - 3) * 2), + reg); + reg = 0; + } + } + + /* Always enable HW pkeys check when pkeys table is set */ + add_rcvctrl(ppd, RCV_CTRL_RCV_PARTITION_KEY_ENABLE_SMASK); +} + +/* + * These CSRs and memories are uninitialized on reset and must be + * written before reading to set the ECC/parity bits. + * + * NOTE: All user context CSRs that are not mmaped write-only + * (e.g. the TID flows) must be initialized even if the driver never + * reads them. + */ +static void write_uninitialized_csrs_and_memories(struct hfi2_devdata *dd) +{ + u32 num_qp_table_regs = dd->params->qp_map_table_entries / + dd->params->qp_map_table_entries_per_csr; + int i, j; + + /* CceIntMap */ + for (i = 0; i < dd->params->num_int_map_csrs; i++) + hfi2_write_csr( + dd, dd->params->cce_msix_int_map_vec_reg + (8 * i), 0); + + /* SendCtxtCreditReturnAddr */ + for (i = 0; i < chip_send_contexts(dd); i++) + write_sctxt_csr( + dd, i, dd->params->send_ctxt_credit_return_addr_reg, 0); + + /* PIO Send buffers */ + /* SDMA Send buffers */ + /* + * These are not normally read, and (presently) have no method + * to be read, so are not pre-initialized + */ + + /* RcvHdrAddr */ + /* RcvHdrTailAddr */ + /* RcvTidFlowTable */ + for (i = 0; i < chip_rcv_contexts(dd); i++) { + write_kctxt_csr(dd, i, dd->params->rcv_hdr_addr_reg, 0); + write_kctxt_csr(dd, i, dd->params->rcv_hdr_tail_addr_reg, 0); + for (j = 0; j < RXE_NUM_TID_FLOWS; j++) + write_uctxt_csr(dd, i, + dd->params->rcv_tid_flow_table_reg + + (8 * j), + 0); + } + + /* RcvArray */ + wfr_init_tids(dd); + + /* RcvQPMapTable */ + for (i = 0; i < dd->num_pports; i++) { + for (j = 0; j < num_qp_table_regs; j++) { + u32 off = dd->params->rcv_qp_map_table_reg + (8 * i); + + write_iport_csr(dd, i, off, 0); + } + } +} + +/* + * Use the ctrl_bits in CceCtrl to clear the status_bits in CceStatus. + */ +static void clear_cce_status(struct hfi2_devdata *dd, u64 status_bits, + u64 ctrl_bits) +{ + unsigned long timeout; + u64 reg; + + /* is the condition present? */ + reg = hfi2_read_csr(dd, CCE_STATUS); + if ((reg & status_bits) == 0) + return; + + /* clear the condition */ + hfi2_write_csr(dd, CCE_CTRL, ctrl_bits); + + /* wait for the condition to clear */ + timeout = jiffies + msecs_to_jiffies(CCE_STATUS_TIMEOUT); + while (1) { + reg = hfi2_read_csr(dd, CCE_STATUS); + if ((reg & status_bits) == 0) + return; + if (time_after(jiffies, timeout)) { + dd_dev_err( + dd, + "Timeout waiting for CceStatus to clear bits 0x%llx, remaining 0x%llx\n", + status_bits, reg & status_bits); + return; + } + udelay(1); + } +} + +/* set CCE CSRs to chip reset defaults */ +static void reset_cce_csrs(struct hfi2_devdata *dd) +{ + int i; + u32 cce_int_clear_reg = dd->params->cce_int_clear_reg; + u32 cce_int_mask_reg = dd->params->cce_int_mask_reg; + + /* CCE_REVISION read-only */ + /* CCE_REVISION2 read-only */ + /* CCE_CTRL - bits clear automatically */ + /* CCE_STATUS read-only, use CceCtrl to clear */ + clear_cce_status(dd, ALL_FROZE, CCE_CTRL_SPC_UNFREEZE_SMASK); + clear_cce_status(dd, ALL_TXE_PAUSE, CCE_CTRL_TXE_RESUME_SMASK); + clear_cce_status(dd, ALL_RXE_PAUSE, CCE_CTRL_RXE_RESUME_SMASK); + for (i = 0; i < CCE_NUM_SCRATCH; i++) + hfi2_write_csr(dd, CCE_SCRATCH + (8 * i), 0); + /* CCE_ERR_STATUS read-only */ + hfi2_write_csr(dd, CCE_ERR_MASK, 0); + hfi2_write_csr(dd, CCE_ERR_CLEAR, ~0ull); + /* CCE_ERR_FORCE leave alone */ + for (i = 0; i < CCE_NUM_32_BIT_COUNTERS; i++) + hfi2_write_csr(dd, CCE_COUNTER_ARRAY32 + (8 * i), 0); + hfi2_write_csr(dd, CCE_DC_CTRL, CCE_DC_CTRL_RESETCSR); + /* CCE_PCIE_CTRL leave alone */ + for (i = 0; i < CCE_NUM_MSIX_VECTORS; i++) { + hfi2_write_csr(dd, CCE_MSIX_TABLE_LOWER + (0x10 * i), 0); + hfi2_write_csr(dd, CCE_MSIX_TABLE_UPPER + (0x10 * i), + CCE_MSIX_TABLE_UPPER_RESETCSR); + } + for (i = 0; i < CCE_NUM_MSIX_PBAS; i++) { + /* CCE_MSIX_PBA read-only */ + hfi2_write_csr(dd, CCE_MSIX_INT_GRANTED, ~0ull); + hfi2_write_csr(dd, CCE_MSIX_VEC_CLR_WITHOUT_INT, ~0ull); + } + for (i = 0; i < dd->params->num_int_map_csrs; i++) + hfi2_write_csr( + dd, dd->params->cce_msix_int_map_vec_reg + (8 * i), 0); + for (i = 0; i < dd->params->num_int_csrs; i++) { + /* CCE_INT_STATUS read-only */ + hfi2_write_csr(dd, cce_int_mask_reg + (8 * i), 0); + hfi2_write_csr(dd, cce_int_clear_reg + (8 * i), ~0ull); + /* CCE_INT_FORCE leave alone */ + /* CCE_INT_BLOCKED read-only */ + } + for (i = 0; i < CCE_NUM_32_BIT_INT_COUNTERS; i++) + hfi2_write_csr(dd, CCE_INT_COUNTER_ARRAY32 + (8 * i), 0); +} + +/* set MISC CSRs to chip reset defaults */ +static void reset_misc_csrs(struct hfi2_devdata *dd) +{ + int i; + + for (i = 0; i < 32; i++) { + hfi2_write_csr(dd, MISC_CFG_RSA_R2 + (8 * i), 0); + hfi2_write_csr(dd, MISC_CFG_RSA_SIGNATURE + (8 * i), 0); + hfi2_write_csr(dd, MISC_CFG_RSA_MODULUS + (8 * i), 0); + } + /* + * MISC_CFG_SHA_PRELOAD leave alone - always reads 0 and can + * only be written 128-byte chunks + */ + /* init RSA engine to clear lingering errors */ + hfi2_write_csr(dd, MISC_CFG_RSA_CMD, 1); + hfi2_write_csr(dd, MISC_CFG_RSA_MU, 0); + hfi2_write_csr(dd, MISC_CFG_FW_CTRL, 0); + /* MISC_STS_8051_DIGEST read-only */ + /* MISC_STS_SBM_DIGEST read-only */ + /* MISC_STS_PCIE_DIGEST read-only */ + /* MISC_STS_FAB_DIGEST read-only */ + /* MISC_ERR_STATUS read-only */ + hfi2_write_csr(dd, MISC_ERR_MASK, 0); + hfi2_write_csr(dd, MISC_ERR_CLEAR, ~0ull); + /* MISC_ERR_FORCE leave alone */ +} + +/* set TXE CSRs to chip reset defaults - only called on WFR */ +static void reset_txe_csrs(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + int i, j; + + /* + * TXE Kernel CSRs + */ + for (i = 0; i < dd->num_pports; i++) { + write_eport_csr(dd, i, dd->params->send_ctrl_reg, 0); + __hfi2_cm_reset(&dd->pport[i], 0); /* reset CM internal state */ + /* SEND_CONTEXTS read-only */ + /* SEND_DMA_ENGINES read-only */ + /* SEND_PIO_MEM_SIZE read-only */ + /* SEND_DMA_MEM_SIZE read-only */ + write_eport_csr(dd, i, dd->params->send_high_priority_limit_reg, + 0); + /* SEND_DMA_ERR_FORCE leave alone */ + /* SEND_EGRESS_ERR_STATUS read-only */ + write_eport_csr(dd, i, dd->params->send_egress_err_mask_reg, 0); + write_eport_csr(dd, i, dd->params->send_egress_err_clear_reg, + ~0ull); + /* SEND_EGRESS_ERR_FORCE leave alone */ + write_eport_csr(dd, i, dd->params->send_bth_qp_reg, 0); + write_eport_csr(dd, i, dd->params->send_static_rate_control_reg, + 0); + write_eport_csr(dd, i, dd->params->send_sc2vlt0_reg, 0); + write_eport_csr(dd, i, dd->params->send_sc2vlt1_reg, 0); + write_eport_csr(dd, i, dd->params->send_sc2vlt2_reg, 0); + write_eport_csr(dd, i, dd->params->send_sc2vlt3_reg, 0); + write_eport_csr(dd, i, dd->params->send_len_check0_reg, 0); + write_eport_csr(dd, i, dd->params->send_len_check1_reg, 0); + for (j = 0; j < VL_ARB_LOW_PRIO_TABLE_SIZE; j++) { + write_eport_csr(dd, i, + dd->params->send_low_priority_list_reg + + (8 * j), + 0); + } + for (j = 0; j < VL_ARB_HIGH_PRIO_TABLE_SIZE; j++) { + write_eport_csr( + dd, i, + dd->params->send_high_priority_list_reg + + (8 * j), + 0); + } + for (j = 0; j < TXE_NUM_32_BIT_COUNTER; j++) { + write_eport_csr(dd, i, + dd->params->send_counter_array32_reg + + (8 * j), + 0); + } + for (j = 0; j < TXE_NUM_64_BIT_COUNTER; j++) { + write_eport_csr(dd, i, + dd->params->send_counter_array64_reg + + (8 * j), + 0); + } + write_eport_csr(dd, i, dd->params->send_cm_ctrl_reg, + SEND_CM_CTRL_RESETCSR); + write_eport_csr(dd, i, dd->params->send_cm_global_credit_reg, + SEND_CM_GLOBAL_CREDIT_RESETCSR); + /* SEND_CM_CREDIT_USED_STATUS read-only */ + write_eport_csr(dd, i, dd->params->send_cm_timer_ctrl_reg, 0); + if (dd->params->chip_type == CHIP_WFR) { + write_eport_csr( + dd, i, + dd->params->send_cm_local_au_table0_to3_reg, 0); + write_eport_csr( + dd, i, + dd->params->send_cm_local_au_table4_to7_reg, 0); + write_eport_csr( + dd, i, + dd->params->send_cm_remote_au_table0_to3_reg, + 0); + write_eport_csr( + dd, i, + dd->params->send_cm_remote_au_table4_to7_reg, + 0); + } + for (j = 0; j < TXE_NUM_DATA_VL; j++) + write_eport_csr( + dd, i, + dd->params->send_cm_credit_vl_reg + (8 * j), 0); + write_eport_csr(dd, i, dd->params->send_cm_credit_vl15_reg, 0); + /* SEND_CM_CREDIT_USED_VL read-only */ + /* SEND_CM_CREDIT_USED_VL15 read-only */ + /* SEND_EGRESS_CTXT_STATUS read-only */ + /* SEND_EGRESS_SEND_DMA_STATUS read-only */ + write_eport_csr(dd, i, dd->params->send_egress_err_info_reg, + ~0ull); + /* SEND_EGRESS_ERR_SOURCE read-only */ + } + hfi2_pio_reset_all(dd); /* SEND_PIO_INIT_CTXT */ + /* SEND_PIO_ERR_STATUS read-only */ + hfi2_write_csr(dd, dd->params->send_pio_err_mask_reg, 0); + hfi2_write_csr(dd, dd->params->send_pio_err_clear_reg, ~0ull); + /* SEND_PIO_ERR_FORCE leave alone */ + /* SEND_DMA_ERR_STATUS read-only */ + hfi2_write_csr(dd, dd->params->send_dma_err_mask_reg, 0); + hfi2_write_csr(dd, dd->params->send_dma_err_clear_reg, ~0ull); + /* SEND_ERR_STATUS read-only */ + hfi2_write_csr(dd, dd->params->csr_err_mask_reg, 0); + hfi2_write_csr(dd, dd->params->csr_err_clear_reg, ~0ull); + /* SEND_ERR_FORCE read-only */ + for (i = 0; i < chip_send_contexts(dd) / NUM_CONTEXTS_PER_SET; i++) + hfi2_write_csr(dd, SEND_CONTEXT_SET_CTRL + (8 * i), 0); + + /* + * TXE Per-Context CSRs + */ + for (i = dr->c.first_send_context; i < dr->c.last_send_context; i++) { + write_tctxt_csr(dd, i, dd->params->send_ctxt_ctrl_reg, 0); + write_sctxt_csr(dd, i, dd->params->send_ctxt_credit_ctrl_reg, + 0); + write_sctxt_csr( + dd, i, dd->params->send_ctxt_credit_return_addr_reg, 0); + write_sctxt_csr(dd, i, dd->params->send_ctxt_credit_force_reg, + 0); + write_sctxt_csr(dd, i, dd->params->send_ctxt_err_mask_reg, 0); + write_sctxt_csr(dd, i, dd->params->send_ctxt_err_clear_reg, + ~0ull); + for (j = 0; j < dd->num_pports; j++) { + write_epsc_csr(dd, j, i, + dd->params->send_ctxt_check_enable_reg, + 0); + write_epsc_csr(dd, j, i, + dd->params->send_ctxt_check_vl_reg, 0); + write_epsc_csr(dd, j, i, + dd->params->send_ctxt_check_job_key_reg, + 0); + write_epsc_csr( + dd, j, i, + dd->params->send_ctxt_check_partition_key_reg, + 0); + write_epsc_csr(dd, j, i, + dd->params->send_ctxt_check_slid_reg, 0); + write_epsc_csr(dd, j, i, + dd->params->send_ctxt_check_opcode_reg, + 0); + } + } + + /* + * TXE Per-SDMA CSRs + */ + for (i = 0; i < chip_sdma_engines(dd); i++) { + write_sdma_csr(dd, i, dd->params->send_dma_ctrl_reg, 0); + /* SEND_DMA_STATUS read-only */ + write_sdma_csr(dd, i, dd->params->send_dma_base_addr_reg, 0); + write_sdma_csr(dd, i, dd->params->send_dma_len_gen_reg, 0); + write_sdma_csr(dd, i, dd->params->send_dma_tail_reg, 0); + /* SEND_DMA_HEAD read-only */ + write_sdma_csr(dd, i, dd->params->send_dma_head_addr_reg, 0); + write_sdma_csr(dd, i, dd->params->send_dma_priority_thld_reg, + 0); + /* SEND_DMA_IDLE_CNT read-only */ + write_sdma_csr(dd, i, dd->params->send_dma_reload_cnt_reg, 0); + write_sdma_csr(dd, i, dd->params->send_dma_desc_cnt_reg, 0); + /* SEND_DMA_DESC_FETCHED_CNT read-only */ + /* SEND_DMA_ENG_ERR_STATUS read-only */ + write_sdma_csr(dd, i, dd->params->send_dma_eng_err_mask_reg, 0); + write_sdma_csr(dd, i, dd->params->send_dma_eng_err_clear_reg, + ~0ull); + /* SEND_DMA_ENG_ERR_FORCE leave alone */ + if (dd->params->chip_type == CHIP_WFR) { + /* SEND_DMA_CHECK_* are WFR only */ + write_kctxt_csr(dd, i, SEND_DMA_CHECK_ENABLE, 0); + write_kctxt_csr(dd, i, SEND_DMA_CHECK_VL, 0); + write_kctxt_csr(dd, i, SEND_DMA_CHECK_JOB_KEY, 0); + write_kctxt_csr(dd, i, SEND_DMA_CHECK_PARTITION_KEY, 0); + write_kctxt_csr(dd, i, SEND_DMA_CHECK_SLID, 0); + write_kctxt_csr(dd, i, SEND_DMA_CHECK_OPCODE, 0); + } + write_sdmacfg_csr(dd, i, dd->params->send_dma_cfg_memory_reg, + 0); + } +} + +/* + * Expect on entry: + * o Packet ingress is disabled, i.e. RcvCtrl.RcvPortEnable == 0 + */ +static void init_rbufs(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg; + int count; + int pidx = ppd->hw_pidx; + + /* + * Wait for DMA to stop: RxRbufPktPending and RxPktInProgress are + * clear. + */ + count = 0; + while (1) { + reg = read_iport_csr(dd, pidx, + dd->params->rcv_iport_status_reg); + if ((reg & (RCV_STATUS_RX_RBUF_PKT_PENDING_SMASK | + RCV_STATUS_RX_PKT_IN_PROGRESS_SMASK)) == 0) + break; + /* + * Give up after 1ms - maximum wait time. + * + * RBuf size is 136KiB. Slowest possible is PCIe Gen1 x1 at + * 250MB/s bandwidth. Lower rate to 66% for overhead to get: + * 136 KB / (66% * 250MB/s) = 844us + */ + if (count++ > 500) { + ppd_dev_err( + ppd, + "%s: in-progress DMA not clearing: RcvStatus 0x%llx, continuing\n", + __func__, reg); + break; + } + udelay(2); /* do not busy-wait the CSR */ + } + + /* start the init - expect RcvCtrl to be 0 */ + write_iport_csr(dd, pidx, dd->params->rcv_iport_ctrl_reg, + RCV_CTRL_RX_RBUF_INIT_SMASK); + + /* + * Read to force the write of RcvCtrl.RxRbufInit. There is a brief + * period after the write before RcvStatus.RxRbufInitDone is valid. + * The delay in the first run through the loop below is sufficient and + * required before the first read of RcvStatus.RxRbufInitDone. + */ + read_iport_csr(dd, pidx, dd->params->rcv_iport_ctrl_reg); + + /* wait for the init to finish */ + count = 0; + while (1) { + /* delay is required first time through - see above */ + udelay(2); /* do not busy-wait the CSR */ + reg = read_iport_csr(dd, pidx, + dd->params->rcv_iport_status_reg); + if (reg & (RCV_STATUS_RX_RBUF_INIT_DONE_SMASK)) + break; + + /* give up after 100us - slowest possible at 33MHz is 73us */ + if (count++ > 50) { + ppd_dev_err( + ppd, + "%s: RcvStatus.RxRbufInit not set, continuing\n", + __func__); + break; + } + } +} + +/* set RXE CSRs to chip reset defaults */ +static void reset_rxe_csrs(struct hfi2_devdata *dd) +{ + u32 num_rmt_csrs = dd->params->rsm_map_table_entries / + dd->params->rsm_map_table_entries_per_csr; + u32 num_qp_table_regs = dd->params->qp_map_table_entries / + dd->params->qp_map_table_entries_per_csr; + int i, j; + + /* + * RXE per-port Kernel CSRs + */ + for (i = 0; i < dd->num_pports; i++) { + struct hfi2_pportdata *ppd = dd->pport + i; + int pidx = ppd->hw_pidx; + + write_iport_csr(dd, pidx, dd->params->rcv_iport_ctrl_reg, 0); + init_rbufs(ppd); + /* RCV_STATUS read-only */ + /* RCV_CONTEXTS read-only */ + /* RCV_ARRAY_CNT read-only */ + /* RCV_BUF_SIZE read-only */ + write_iport_csr(dd, pidx, dd->params->rcv_bth_qp_reg, 0); + write_iport_csr(dd, pidx, dd->params->rcv_multicast_reg, 0); + write_iport_csr(dd, pidx, dd->params->rcv_bypass_reg, 0); + write_iport_csr(dd, pidx, dd->params->rcv_vl15_reg, 0); + /* this is a clear-down */ + write_iport_csr(dd, pidx, dd->params->rcv_err_info_reg, + RCV_ERR_INFO_RCV_EXCESS_BUFFER_OVERRUN_SMASK); + /* RCV_ERR_STATUS read-only */ + write_iport_csr(dd, pidx, dd->params->rcv_err_mask_reg, 0); + write_iport_csr(dd, pidx, dd->params->rcv_err_clear_reg, ~0ull); + /* RCV_ERR_FORCE leave alone */ + for (i = 0; i < num_qp_table_regs; i++) + write_iport_csr( + dd, pidx, + dd->params->rcv_qp_map_table_reg + (8 * i), 0); + for (i = 0; i < 4; i++) + write_iport_csr( + dd, pidx, + dd->params->rcv_partition_key_reg + (8 * i), 0); + for (i = 0; i < RXE_NUM_32_BIT_COUNTERS; i++) + write_iport_csr(dd, pidx, + dd->params->rcv_counter_array32_reg + + (8 * i), + 0); + for (i = 0; i < RXE_NUM_64_BIT_COUNTERS; i++) + write_iport_csr(dd, pidx, + dd->params->rcv_counter_array64_reg + + (8 * i), + 0); + } + + /* + * Clear all RSM rules from index 0. This function is called during + * chip reset where all rules must be cleared regardless of which + * rules are currently in use. + */ + for (i = 0; i < dd->params->rsm_rule_size; i++) + clear_rsm_rule(dd, i); + for (i = 0; i < num_rmt_csrs; i++) + hfi2_write_csr(dd, dd->params->rcv_rsm_map_table_reg + (8 * i), + 0); + + /* + * RXE Kernel and User Per-Context CSRs + */ + for (i = 0; i < chip_rcv_contexts(dd); i++) { + /* kernel */ + write_kctxt_csr(dd, i, dd->params->rcv_kctxt_ctrl_reg, 0); + write_rctxt_csr(dd, i, dd->params->rcv_rctxt_ctrl_reg, 0); + /* RCV_CTXT_STATUS read-only */ + write_rctxt_csr(dd, i, dd->params->rcv_egr_ctrl_reg, 0); + write_rctxt_csr(dd, i, dd->params->rcv_tid_ctrl_reg, 0); + for (j = 0; j < dd->num_pports; j++) + write_iprc_csr(dd, j, i, dd->params->rcv_jkey_ctrl_reg, + 0); + write_kctxt_csr(dd, i, dd->params->rcv_hdr_addr_reg, 0); + if (dd->params->set_rheq_addr) + dd->params->set_rheq_addr(dd, i, 0); + write_kctxt_csr(dd, i, dd->params->rcv_hdr_cnt_reg, 0); + write_kctxt_csr(dd, i, dd->params->rcv_hdr_ent_size_reg, 0); + if (dd->params->chip_type == CHIP_WFR) + write_kctxt_csr(dd, i, RCV_HDR_SIZE, 0); + write_kctxt_csr(dd, i, dd->params->rcv_hdr_tail_addr_reg, 0); + write_kctxt_csr(dd, i, dd->params->rcv_avail_time_out_reg, 0); + write_kctxt_csr(dd, i, dd->params->rcv_hdr_ovfl_cnt_reg, 0); + + /* user */ + /* RCV_HDR_TAIL read-only */ + write_uctxt_csr(dd, i, dd->params->rcv_hdr_head_reg, 0); + /* RCV_EGR_INDEX_TAIL read-only */ + write_uctxt_csr(dd, i, dd->params->rcv_egr_index_head_reg, 0); + /* RCV_EGR_OFFSET_TAIL read-only */ + for (j = 0; j < RXE_NUM_TID_FLOWS; j++) { + write_uctxt_csr(dd, i, + dd->params->rcv_tid_flow_table_reg + + (8 * j), + 0); + } + } +} + +/* + * Set sc2vl tables. + * + * They power on to zeros, so to avoid send context errors + * they need to be set: + * + * SC 0-7 -> VL 0-7 (respectively) + * SC 15 -> VL 15 + * otherwise + * -> VL 0 + */ +static void init_sc2vl_tables(struct hfi2_devdata *dd) +{ + int i; + int j; + /* init per architecture spec, constrained by hardware capability */ + + if (dd->params->chip_type != CHIP_WFR) { + if (dd->is_vf) + return; + /* cport is active - read the current sc2vlt */ + for (i = 0; i < dd->num_pports; i++) { + struct hfi2_pportdata *ppd = &dd->pport[i]; + + hfi2_get_sc2vlt_tables(ppd, ppd->sc2vl); + } + + return; + } + + /* HFI maps sent packets */ + for (i = 0; i < dd->num_pports; i++) { + write_eport_csr(dd, i, dd->params->send_sc2vlt0_reg, + SC2VL_VAL(0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, + 6, 6, 7, 7)); + write_eport_csr(dd, i, dd->params->send_sc2vlt1_reg, + SC2VL_VAL(1, 8, 0, 9, 0, 10, 0, 11, 0, 12, 0, + 13, 0, 14, 0, 15, 15)); + write_eport_csr(dd, i, dd->params->send_sc2vlt2_reg, + SC2VL_VAL(2, 16, 0, 17, 0, 18, 0, 19, 0, 20, 0, + 21, 0, 22, 0, 23, 0)); + write_eport_csr(dd, i, dd->params->send_sc2vlt3_reg, + SC2VL_VAL(3, 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, + 29, 0, 30, 0, 31, 0)); + } + + /* DC maps received packets */ + hfi2_write_csr(dd, DCC_CFG_SC_VL_TABLE_15_0, + DC_SC_VL_VAL(15_0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, + 6, 7, 7, 8, 0, 9, 0, 10, 0, 11, 0, 12, 0, + 13, 0, 14, 0, 15, 15)); + hfi2_write_csr(dd, DCC_CFG_SC_VL_TABLE_31_16, + DC_SC_VL_VAL(31_16, 16, 0, 17, 0, 18, 0, 19, 0, 20, 0, + 21, 0, 22, 0, 23, 0, 24, 0, 25, 0, 26, 0, + 27, 0, 28, 0, 29, 0, 30, 0, 31, 0)); + + /* initialize the cached sc2vl values consistently with h/w */ + for (i = 0; i < dd->num_pports; i++) { + struct hfi2_pportdata *ppd = &dd->pport[i]; + + for (j = 0; j < 32; j++) { + if (j < 8 || j == 15) + *((u8 *)(ppd->sc2vl) + j) = (u8)j; + else + *((u8 *)(ppd->sc2vl) + j) = 0; + } + } +} + +/* + * Read chip sizes and then reset parts to sane, disabled, values. We cannot + * depend on the chip going through a power-on reset - a driver may be loaded + * and unloaded many times. + * + * Do not write any CSR values to the chip in this routine - there may be + * a reset following the (possible) FLR in this routine. + * + */ +static int init_chip(struct hfi2_devdata *dd) +{ + int i; + int ret = 0; + u32 cce_int_mask_reg = dd->params->cce_int_mask_reg; + + /* + * Put the HFI CSRs in a known state. + * Combine this with a DC reset. + * + * Stop the device from doing anything while we do a + * reset. We know there are no other active users of + * the device since we are now in charge. Turn + * off all outbound and inbound traffic and make sure + * the device does not generate any interrupts. + */ + + /* disable send contexts and SDMA engines */ + for (i = 0; i < dd->num_pports; i++) + write_eport_csr(dd, i, dd->params->send_ctrl_reg, 0); + for (i = 0; i < chip_send_contexts(dd); i++) + write_tctxt_csr(dd, i, dd->params->send_ctxt_ctrl_reg, 0); + for (i = 0; i < chip_sdma_engines(dd); i++) + write_sdma_csr(dd, i, dd->params->send_dma_ctrl_reg, 0); + /* disable port (turn off RXE inbound traffic) and contexts */ + for (i = 0; i < dd->num_pports; i++) + write_iport_csr(dd, i, dd->params->rcv_iport_ctrl_reg, 0); + for (i = 0; i < chip_rcv_contexts(dd); i++) { + write_kctxt_csr(dd, i, dd->params->rcv_kctxt_ctrl_reg, 0); + write_rctxt_csr(dd, i, dd->params->rcv_rctxt_ctrl_reg, 0); + } + /* mask all interrupt sources */ + for (i = 0; i < dd->params->num_int_csrs; i++) + hfi2_write_csr(dd, cce_int_mask_reg + (8 * i), 0ull); + + /* + * DC Reset: do a full DC reset before the register clear. + * A recommended length of time to hold is one CSR read, + * so reread the CceDcCtrl. Then, hold the DC in reset + * across the clear. + */ + hfi2_write_csr(dd, CCE_DC_CTRL, CCE_DC_CTRL_DC_RESET_SMASK); + (void)hfi2_read_csr(dd, CCE_DC_CTRL); + + if (use_flr) { + /* + * A FLR will reset the SPC core and part of the PCIe. + * The parts that need to be restored have already been + * saved. + */ + dd_dev_info(dd, "Resetting CSRs with FLR\n"); + + /* do the FLR, the DC reset will remain */ + pcie_flr(dd->pcidev); + + /* restore command and BARs */ + ret = hfi2_restore_pci_variables(dd); + if (ret) { + dd_dev_err(dd, "%s: Could not restore PCI variables\n", + __func__); + return ret; + } + + if (hfi2_is_ax(dd)) { + dd_dev_info(dd, "Resetting CSRs with FLR\n"); + pcie_flr(dd->pcidev); + ret = hfi2_restore_pci_variables(dd); + if (ret) { + dd_dev_err( + dd, + "%s: Could not restore PCI variables\n", + __func__); + return ret; + } + } + } else { + dd_dev_info(dd, "Resetting CSRs with writes\n"); + reset_cce_csrs(dd); + reset_txe_csrs(dd); + reset_rxe_csrs(dd); + reset_misc_csrs(dd); + } + /* clear the DC reset */ + hfi2_write_csr(dd, CCE_DC_CTRL, 0); + + /* Turn off LEDs */ + for (i = 0; i < dd->num_pports; i++) + dd->params->hfi2_setextled(&dd->pport[i], 0); + + /* + * Clear the QSFP reset. + * An FLR enforces a 0 on all out pins. The driver does not touch + * ASIC_QSFPn_OUT otherwise. This leaves RESET_N low and + * anything plugged constantly in reset, if it pays attention + * to RESET_N. + * Prime examples of this are optical cables. Set all pins high. + * I2CCLK and I2CDAT will change per direction, and INT_N and + * MODPRS_N are input only and their value is ignored. + */ + hfi2_write_csr(dd, ASIC_QSFP1_OUT, 0x1f); + hfi2_write_csr(dd, ASIC_QSFP2_OUT, 0x1f); + hfi2_init_chip_resources(dd); + return ret; +} + +void hfi2_init_early_variables(struct hfi2_devdata *dd) +{ + int i; + + /* assign link credit variables */ + dd->vau = CM_VAU; + dd->link_credits = CM_GLOBAL_CREDITS; + if (hfi2_is_ax(dd)) + dd->link_credits--; + dd->vcu = cu_to_vcu(hfi2_cu); + /* enough room for 8 MAD packets plus header - 17K */ + dd->vl15_init = (8 * (2048 + 128)) / vau_to_au(dd->vau); + if (dd->vl15_init > dd->link_credits) + dd->vl15_init = dd->link_credits; + + if (HFI2_CAP_IS_KSET(PKEY_CHECK)) + for (i = 0; i < dd->num_pports; i++) { + struct hfi2_pportdata *ppd = &dd->pport[i]; + + set_partition_keys(ppd); + } + init_sc2vl_tables(dd); +} + +void hfi2_init_kdeth_qp(struct hfi2_devdata *dd) +{ + u64 val; + int i; + + if (dd->is_vf) + return; /* Only PF0 does this */ + + for (i = 0; i < dd->num_pports; i++) { + val = (RVT_KDETH_QP_PREFIX & SEND_BTH_QP_KDETH_QP_MASK) + << SEND_BTH_QP_KDETH_QP_SHIFT; + write_eport_csr(dd, i, dd->params->send_bth_qp_reg, val); + if (dd->is_sriov) + write_eport_csr(dd, loopback_pidx_dd(dd, i), + dd->params->send_bth_qp_reg, val); + + val = (RVT_KDETH_QP_PREFIX & RCV_BTH_QP_KDETH_QP_MASK) + << RCV_BTH_QP_KDETH_QP_SHIFT; + write_iport_csr(dd, i, dd->params->rcv_bth_qp_reg, val); + if (dd->is_sriov) + write_iport_csr(dd, loopback_pidx_dd(dd, i), + dd->params->rcv_bth_qp_reg, val); + } +} + +/** + * hfi2_get_qp_map - get qp map entry + * @ppd: port data + * @idx: index to read (will be masked to table size) + */ +u16 hfi2_get_qp_map(struct hfi2_pportdata *ppd, u16 idx) +{ + struct hfi2_devdata *dd = ppd->dd; + u32 tbl_idx; + u32 reg_idx; + u32 entry_idx; + u32 off; + u64 reg; + + if (dd->is_vf) + return hfi2_vf2pf_get_qp_map(dd, ppd->hw_pidx, idx); + + tbl_idx = idx & (dd->params->qp_map_table_entries - 1); + reg_idx = tbl_idx / dd->params->qp_map_table_entries_per_csr; + entry_idx = tbl_idx % dd->params->qp_map_table_entries_per_csr; + + off = dd->params->rcv_qp_map_table_reg + (reg_idx * 8); + reg = read_iport_csr(dd, ppd->hw_pidx, off); + + return (reg >> (entry_idx * dd->params->qp_map_table_entry_shift)) & + dd->params->qp_map_table_entry_mask; +} + +/** + * init_qpmap_table - init qp map + * @ppd: port data + * @first_ctxt: first context + * @last_ctxt: last context + * + * This return sets the qpn mapping table that + * is indexed by qpn[8:1]. + * + * The routine will round robin the 256 settings + * from first_ctxt to last_ctxt. + * + * The first/last looks ahead to having specialized + * receive contexts for mgmt and bypass. Normal + * verbs traffic will assumed to be on a range + * of receive contexts. + * + * Assumes hfi2_loopback QP map is identical to fabric port QP map. + */ +static void init_qpmap_table(struct hfi2_pportdata *ppd, u32 first_ctxt, + u32 last_ctxt) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg = 0; + u64 ctxt = first_ctxt; + u32 regno = dd->params->rcv_qp_map_table_reg; + u32 entry_shift = dd->params->qp_map_table_entry_shift; + u32 entry_top = dd->params->qp_map_table_entries_per_csr - 1; + int count = dd->params->qp_map_table_entries; + int i; + + for (i = 0; i < count; i++) { + u32 entry_idx = i % dd->params->qp_map_table_entries_per_csr; + + reg |= ctxt << (entry_shift * entry_idx); + ctxt++; + if (ctxt > last_ctxt) + ctxt = first_ctxt; + if (entry_idx == entry_top) { + write_iport_csr(dd, ppd->hw_pidx, regno, reg); + if (dd->is_sriov) + write_iport_csr(dd, loopback_pidx(ppd), regno, + reg); + reg = 0; + regno += 8; + } + } + + add_rcvctrl(ppd, RCV_CTRL_RCV_QP_MAP_ENABLE_SMASK | + RCV_CTRL_RCV_BYPASS_ENABLE_SMASK); +} + +/* + * Assumes hfi2_loopback QP map is identical to fabric port QP map. + */ +static void init_qpmap_table_range(struct hfi2_pportdata *ppd, u32 start_idx, + u32 end_idx, u32 first_ctxt, u32 last_ctxt) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 reg = 0; + u32 regno; + int i; + u64 ctxt = first_ctxt; + bool valid = false; + + regno = dd->params->rcv_qp_map_table_reg + (start_idx & ~7); + for (i = start_idx; i < end_idx; i++) { + if (!valid) { + reg = read_iport_csr(dd, ppd->hw_pidx, regno); + valid = true; + } + reg &= ~(0xffull << (8 * (i % 8))); + reg |= ctxt << (8 * (i % 8)); + ctxt++; + if (ctxt > last_ctxt) + ctxt = first_ctxt; + if (i % 8 == 7) { + write_iport_csr(dd, ppd->hw_pidx, regno, reg); + if (dd->is_sriov) + write_iport_csr(dd, loopback_pidx(ppd), regno, + reg); + reg = 0; + regno += 8; + valid = false; + } + } + if (valid) { + write_iport_csr(dd, ppd->hw_pidx, regno, reg); + if (dd->is_sriov) + write_iport_csr(dd, loopback_pidx(ppd), regno, reg); + } +} + +struct rsm_map_table { + unsigned int used; + u64 map[]; +}; + +static void set_rmt_entry(struct hfi2_devdata *dd, struct rsm_map_table *rmt, + u16 idx, u16 value); + +struct rsm_rule_data { + u16 offset; + u8 pkt_type; + u8 pidx_mask; + u32 field1_off; + u32 field2_off; + u32 index1_off; + u32 index1_width; + u32 index2_off; + u32 index2_width; + u32 mask1; + u32 value1; + u32 mask2; + u32 value2; +}; + +/* + * Return an initialized RMT map table for users to fill in. OK if it + * returns NULL, indicating no table. + */ +static struct rsm_map_table *alloc_rsm_map_table(struct hfi2_devdata *dd) +{ + struct rsm_map_table *rmt; + u32 num_rmt_csrs = dd->params->rsm_map_table_entries / + dd->params->rsm_map_table_entries_per_csr; + /* 0 is default if a0 version */ + u16 rxcontext = hfi2_is_ax(dd) ? 0 : + dd->params->rsm_map_table_entry_mask; + u32 i; + + rmt = kmalloc(sizeof(*rmt) + (sizeof(rmt->map[0]) * num_rmt_csrs), + GFP_KERNEL); + if (rmt) { + for (i = 0; i < dd->params->rsm_map_table_entries; ++i) + set_rmt_entry(dd, rmt, i, rxcontext); + rmt->used = 0; + } + + return rmt; +} + +/* + * Write the final RSM map table to the chip and enable RSM on each port. + */ +static void complete_rsm_map_table(struct hfi2_devdata *dd, + struct rsm_map_table *rmt) +{ + u32 num_rmt_csrs = dd->params->rsm_map_table_entries / + dd->params->rsm_map_table_entries_per_csr; + int i; + + /* write table to chip */ + for (i = 0; i < num_rmt_csrs; i++) + hfi2_write_csr(dd, dd->params->rcv_rsm_map_table_reg + (8 * i), + rmt->map[i]); + + /* enable RSM on each port */ + for (i = 0; i < dd->num_pports; i++) + add_rcvctrl(dd->pport + i, RCV_CTRL_RCV_RSM_ENABLE_SMASK); +} + +/* + * Add a receive side mapping rule. + */ +static void add_rsm_rule(struct hfi2_devdata *dd, u8 rule_index, + struct rsm_rule_data *rrd) +{ + hfi2_write_csr( + dd, dd->params->rcv_rsm_cfg_reg + (8 * rule_index), + (u64)rrd->offset << dd->params->rsm_rule_offset_shift | + (u64)rrd->pidx_mask + << 40 | /* port enable mask (non WFR) */ + 1ull << (rule_index % 4) | /* enable bit, no chain */ + (u64)rrd->pkt_type << RCV_RSM_CFG_PACKET_TYPE_SHIFT); + hfi2_write_csr(dd, dd->params->rcv_rsm_select_reg + (8 * rule_index), + (u64)rrd->field1_off + << RCV_RSM_SELECT_FIELD1_OFFSET_SHIFT | + (u64)rrd->field2_off + << RCV_RSM_SELECT_FIELD2_OFFSET_SHIFT | + (u64)rrd->index1_off + << RCV_RSM_SELECT_INDEX1_OFFSET_SHIFT | + (u64)rrd->index1_width + << RCV_RSM_SELECT_INDEX1_WIDTH_SHIFT | + (u64)rrd->index2_off + << RCV_RSM_SELECT_INDEX2_OFFSET_SHIFT | + (u64)rrd->index2_width + << RCV_RSM_SELECT_INDEX2_WIDTH_SHIFT); + hfi2_write_csr(dd, dd->params->rcv_rsm_match_reg + (8 * rule_index), + (u64)rrd->mask1 << RCV_RSM_MATCH_MASK1_SHIFT | + (u64)rrd->value1 << RCV_RSM_MATCH_VALUE1_SHIFT | + (u64)rrd->mask2 << RCV_RSM_MATCH_MASK2_SHIFT | + (u64)rrd->value2 << RCV_RSM_MATCH_VALUE2_SHIFT); +} + +/* + * Clear a receive side mapping rule. + */ +static void clear_rsm_rule(struct hfi2_devdata *dd, int rule_index) +{ + if (rule_index < 0 || rule_index >= dd->params->rsm_rule_size) + return; + + hfi2_write_csr(dd, dd->params->rcv_rsm_cfg_reg + (8 * rule_index), 0); + hfi2_write_csr(dd, dd->params->rcv_rsm_select_reg + (8 * rule_index), + 0); + hfi2_write_csr(dd, dd->params->rcv_rsm_match_reg + (8 * rule_index), 0); + if (test_and_clear_bit(rule_index, dd->rsm_rule_bitmap) == 0) { + dd_dev_err(dd, "%s: rule_index %d not set\n", __func__, + rule_index); + } +} + +/* + * Release all allocated rules. + * Expect to be called at driver unload time. + */ +void hfi2_release_rsm_rules(struct hfi2_devdata *dd) +{ + int i; + + if (!dd->rsm_rule_init) + return; + + for (i = dd->first_rsm_rule; i < dd->params->rsm_rule_size; i++) { + if (test_bit(i, dd->rsm_rule_bitmap)) + clear_rsm_rule(dd, i); + } + dd->rsm_rule_init = false; +} + +/* + * Allocate a RSM rule index. + * Returns >=0 rule index, or -ENOSPC if no room + */ +static int alloc_rsm_rule(struct hfi2_devdata *dd, int type) +{ + unsigned long rule_index; + + /* loop in case there is an allocate race */ + while (1) { + rule_index = find_first_zero_bit(dd->rsm_rule_bitmap, + dd->params->rsm_rule_size); + if (rule_index >= dd->params->rsm_rule_size) { + dd_dev_err(dd, "Unable to allocate rule for type %d\n", + type); + return -ENOSPC; + } + + if (test_and_set_bit(rule_index, dd->rsm_rule_bitmap) == 0) + break; + } + return (int)rule_index; +} + +/* return the number of RSM map table entries that will be used for QOS */ +static int qos_rmt_entries(unsigned int n_krcv_queues, unsigned int *mp, + unsigned int *np) +{ + int i; + unsigned int m, n; + uint max_by_vl = 0; + + /* is QOS active at all? */ + if (n_krcv_queues < MIN_KERNEL_KCTXTS || hfi2_num_vls == 1 || + hfi2_krcvqsset <= 1) + goto no_qos; + + /* determine bits for qpn */ + for (i = 0; i < min_t(unsigned int, hfi2_num_vls, hfi2_krcvqsset); i++) + if (hfi2_krcvqs[i] > max_by_vl) + max_by_vl = hfi2_krcvqs[i]; + if (max_by_vl > 32) + goto no_qos; + m = ilog2(__roundup_pow_of_two(max_by_vl)); + + /* determine bits for vl */ + n = ilog2(__roundup_pow_of_two(hfi2_num_vls)); + + /* reject if too much is used */ + if ((m + n) > 7) + goto no_qos; + + if (mp) + *mp = m; + if (np) + *np = n; + + return 1 << (m + n); + +no_qos: + if (mp) + *mp = 0; + if (np) + *np = 0; + return 0; +} + +/** + * init_qos_port - initialize RX QOS for a single port + * @ppd: port data + * @rmt: RSM map table + * + * This routine initializes a rule and the RSM map table to implement + * quality of service (qos) for this port. + * + * If all of the limit tests succeed, qos is applied based on the array + * interpretation of hfi2_krcvqs where entry 0 is VL0. + * + * The number of vl bits (n) and the number of qpn bits (m) are computed to + * feed both the RSM map table and the single rule. + */ +static void init_qos_port(struct hfi2_pportdata *ppd, struct rsm_map_table *rmt) +{ + struct hfi2_devdata *dd = ppd->dd; + struct hfi2_devrsrcs *dr = &dd->rsrcs; + struct hfi2_portrsrcs *pr = &dr->ppr[ppd->hw_pidx]; + unsigned int rcb = pr->rcv_context_base; + struct rsm_rule_data rrd; + unsigned int qpns_per_vl, ctxt, i, qpn, n, m; + unsigned int rmt_entries; + int rule_index; + + if (!rmt) + goto bail; + if (!pr->n_krcv_queues) + goto bail; + rmt_entries = qos_rmt_entries(pr->n_krcv_queues - 1, &m, &n); + if (rmt_entries == 0) + goto bail; + qpns_per_vl = 1 << m; + + /* enough room in the map table? */ + if (rmt->used + rmt_entries > dd->params->rsm_map_table_entries) + goto bail; + + /* allocate a rule */ + rule_index = alloc_rsm_rule(dd, RSM_TYPE_VERBS); + if (rule_index < 0) + goto bail; + + /* fill block in RMT with this port's control context */ + ctxt = rcb + HFI2_CTRL_CTXT; + for (i = 0; i < rmt_entries; i++) + set_rmt_entry(dd, rmt, rmt->used + i, ctxt); + + /* overwrite applicable qos entries */ + ctxt = rcb + FIRST_KERNEL_KCTXT; + for (i = 0; i < hfi2_num_vls; i++) { + unsigned int tctxt; + unsigned int idx; + + for (qpn = 0, tctxt = ctxt; hfi2_krcvqs[i] && qpn < qpns_per_vl; + qpn++) { + /* generate the index the hardware will produce */ + idx = rmt->used + ((qpn << n) ^ i); + set_rmt_entry(dd, rmt, idx, tctxt); + tctxt++; + if (tctxt == ctxt + hfi2_krcvqs[i]) + tctxt = ctxt; + } + ctxt += hfi2_krcvqs[i]; + } + + /* + * Create a rule to extract an index using the formula: + * idx = qpn[m+n+1:1] ^ VL[n-1:0] + * + * The "qos_shift" setting will ensure the bottom n+1 bits of the QPN + * will always be zero (except for GSI QPN=1). + */ + rrd.offset = rmt->used; + rrd.pkt_type = RHF_RCV_TYPE_IB; + rrd.pidx_mask = 1 << ppd->hw_pidx; + rrd.field1_off = LRH_BTH_MATCH_OFFSET; + rrd.field2_off = LRH_SC_MATCH_OFFSET; + rrd.index1_off = LRH_SC_SELECT_OFFSET; + rrd.index1_width = n; + rrd.index2_off = QPN_SELECT_OFFSET; + rrd.index2_width = m + n; + rrd.mask1 = LRH_BTH_MASK; + rrd.value1 = LRH_BTH_VALUE; + rrd.mask2 = LRH_SC_MASK; + rrd.value2 = LRH_SC_VALUE; + + /* add rule */ + add_rsm_rule(dd, rule_index, &rrd); + + /* mark RSM map entries as used */ + rmt->used += rmt_entries; + + ppd->qos_shift = n + 1; + + /* map everything else to this port's mcast/err/vl15 context */ + init_qpmap_table(ppd, rcb + HFI2_CTRL_CTXT, rcb + HFI2_CTRL_CTXT); + return; +bail: + ppd->qos_shift = 1; + + if (pr->n_krcv_queues) { + /* map everything to this port's kernel contexts (excl. HFI2_CTRL_CTXT) */ + ctxt = rcb + FIRST_KERNEL_KCTXT; + init_qpmap_table(ppd, ctxt, + ctxt + pr->n_krcv_queues - FIRST_KERNEL_KCTXT - + 1); + } +} + +void hfi2_restore_qpmap_table(struct hfi2_devdata *dd) +{ + struct hfi2_pportdata *ppd; + struct hfi2_portrsrcs *pr; + int i; + unsigned int rcb, ctxt; + + for (i = 0; i < dd->num_pports; i++) { + pr = &dd->rsrcs.ppr[i]; + if (!pr->n_krcv_queues) + continue; + ppd = &dd->pport[i]; + rcb = pr->rcv_context_base; + if (ppd->qos_shift == 1) { + ctxt = rcb + FIRST_KERNEL_KCTXT; + init_qpmap_table(ppd, ctxt, + ctxt + pr->n_krcv_queues - + FIRST_KERNEL_KCTXT - 1); + } else { + init_qpmap_table(ppd, rcb + HFI2_CTRL_CTXT, + rcb + HFI2_CTRL_CTXT); + } + } +} + +static void init_qos(struct hfi2_devdata *dd, struct rsm_map_table *rmt) +{ + int i; + + for (i = 0; i < dd->num_pports; i++) + init_qos_port(&dd->pport[i], rmt); +} + +/* set a single RSM Map Table (RMT) entry in the given map */ +static void set_rmt_entry(struct hfi2_devdata *dd, struct rsm_map_table *rmt, + u16 idx, u16 value) +{ + u64 reg; + int regoff, regidx; + u32 entries_per_csr = dd->params->rsm_map_table_entries_per_csr; + u32 entry_mask = dd->params->rsm_map_table_entry_mask; + u32 entry_shift = dd->params->rsm_map_table_entry_shift; + + regoff = ((int)idx % entries_per_csr) * entry_shift; + regidx = (int)idx / entries_per_csr; + reg = rmt->map[regidx]; + reg &= ~((u64)entry_mask << regoff); + reg |= ((u64)(value & entry_mask) << regoff); + rmt->map[regidx] = reg; +} + +static void init_fecn_handling(struct hfi2_pportdata *ppd, + struct rsm_map_table *rmt) +{ + struct hfi2_devdata *dd = ppd->dd; + struct hfi2_devrsrcs *dr = &dd->rsrcs; + struct hfi2_portrsrcs *pr = &dr->ppr[ppd->hw_pidx]; + struct rsm_rule_data rrd; + int i, idx, start, end; + u16 offset; + u32 total_cnt; + int rule_index; + + /* do nothing if port is not available */ + if (!port_available_ppd(ppd)) + return; + + if (HFI2_CAP_IS_KSET(TID_RDMA)) + /* Exclude control context */ + start = pr->rcv_context_base + 1; + else + start = pr->first_dyn_alloc_ctxt; + end = pr->rcv_context_base + pr->num_rcv_contexts; + + total_cnt = end - start; + + /* there needs to be enough room in the map table */ + if (rmt->used + total_cnt > dd->params->rsm_map_table_entries) { + ppd_dev_err( + ppd, + "FECN handling disabled - too many contexts allocated\n"); + return; + } + + rule_index = alloc_rsm_rule(dd, RSM_TYPE_FECN); + if (rule_index < 0) + return; + + /* + * RSM will extract the destination context as an index into the + * map table. The destination contexts are a sequential block + * in the range start..end-1 (inclusive). + * Map entries are accessed as offset + extracted value. Adjust + * the added offset so this sequence can be placed anywhere in + * the table - as long as the entries themselves do not wrap. + * There are only enough bits in offset for the table size, so + * start with that to allow for a "negative" offset. + */ + offset = (dd->params->rsm_map_table_entries + rmt->used - start) & + dd->params->rsm_map_table_entry_mask; + + for (i = start, idx = rmt->used; i < end; i++, idx++) { + /* replace with identity mapping */ + set_rmt_entry(dd, rmt, idx, i); + } + + /* + * For RSM intercept of Expected FECN packets: + * o packet type 0 - expected + * o match on F (bit 95), using select/match 1, and + * o match on SH (bit 133), using select/match 2. + * + * Use index 1 to extract the 8-bit receive context from DestQP + * (start at bit 64). Use that as the RSM map table index. + */ + rrd.offset = offset; + rrd.pkt_type = RHF_RCV_TYPE_EXPECTED; + rrd.pidx_mask = 1 << ppd->hw_pidx; + rrd.field1_off = 95; + rrd.field2_off = 133; + rrd.index1_off = 64; + rrd.index1_width = 8; + rrd.index2_off = 0; + rrd.index2_width = 0; + rrd.mask1 = 1; + rrd.value1 = 1; + rrd.mask2 = 1; + rrd.value2 = 1; + + add_rsm_rule(dd, rule_index, &rrd); + + rmt->used += total_cnt; +} + +static inline int hfi2_netdev_set_free_rmt_idx(struct hfi2_pportdata *ppd, + struct rsm_map_table *rmt) +{ + if (rmt->used + NUM_NETDEV_MAP_ENTRIES > + ppd->dd->params->rsm_map_table_entries) { + ppd_dev_err(ppd, "Not enough RMT entries, used = %d\n", + rmt->used); + return -ENOSPC; + } + ppd->netdev_rx->rmt_start = rmt->used; + rmt->used += NUM_NETDEV_MAP_ENTRIES; + return 0; +} + +static void hfi2_netdev_update_rmt(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + int i, j; + int ctx_id = 0; + u8 left_shift; + u64 reg; + u32 regoff; + int rmt_start = hfi2_netdev_get_free_rmt_idx(ppd); + int ctxt_count = hfi2_netdev_ctxt_count(ppd); + u32 entries_per_csr = ppd->dd->params->rsm_map_table_entries_per_csr; + u32 entry_mask = ppd->dd->params->rsm_map_table_entry_mask; + u32 entry_shift = ppd->dd->params->rsm_map_table_entry_shift; + u32 ctxt; + + dev_dbg(&(dd)->pcidev->dev, "RMT start = %d, end %d\n", rmt_start, + rmt_start + NUM_NETDEV_MAP_ENTRIES); + + /* Update RSM mapping table */ + regoff = dd->params->rcv_rsm_map_table_reg + + ((rmt_start / entries_per_csr) * 8); + reg = hfi2_read_csr(dd, regoff); + for (i = 0; i < NUM_NETDEV_MAP_ENTRIES; i++) { + /* Update map register with netdev context */ + j = (rmt_start + i) % entries_per_csr; + left_shift = j * entry_shift; + ctxt = hfi2_netdev_get_ctxt(ppd, ctx_id++)->ctxt; + reg &= ~((u64)entry_mask << left_shift); + reg |= ((u64)(ctxt & entry_mask) << left_shift); + /* Wrap up netdev ctx index */ + ctx_id %= ctxt_count; + + /* Write back map register */ + if ((j == entries_per_csr - 1) || + ((i + 1) == NUM_NETDEV_MAP_ENTRIES)) { + dev_dbg(&(dd)->pcidev->dev, "RMT[%d] = 0x%llx\n", + (regoff - dd->params->rcv_rsm_map_table_reg) / + entries_per_csr, + reg); + + hfi2_write_csr(dd, regoff, reg); + if (i < (NUM_NETDEV_MAP_ENTRIES - 1)) { + regoff += 8; + reg = hfi2_read_csr(dd, regoff); + } + } + } +} + +static void hfi2_enable_rsm_rule(struct hfi2_pportdata *ppd, int type, + struct rsm_rule_data *rrd) +{ + struct hfi2_devdata *dd = ppd->dd; + int rule_index; + + if (dd->is_vf) { + ppd_dev_err(ppd, "VFs can't yet setup RSM rules\n"); + return; + } + /* lock is for setting netdev_rsm_rule */ + mutex_lock(&hfi2_mutex); + if (ppd->netdev_rsm_rule >= 0) { + ppd_dev_info(ppd, + "Netdev contexts are already mapped in RMT\n"); + goto done; + } + + rule_index = alloc_rsm_rule(dd, type); + if (rule_index < 0) + goto done; + + hfi2_netdev_update_rmt(ppd); + + ppd->netdev_rsm_rule = rule_index; + add_rsm_rule(dd, rule_index, rrd); + add_rcvctrl(ppd, RCV_CTRL_RCV_RSM_ENABLE_SMASK); + +done: + mutex_unlock(&hfi2_mutex); +} + +void hfi2_init_aip_rsm(struct hfi2_pportdata *ppd) +{ + /* + * go through with the initialisation only if this rule actually doesn't + * exist yet + */ + if (atomic_fetch_inc(&ppd->ipoib_rsm_usr_num) == 0) { + int rmt_start = hfi2_netdev_get_free_rmt_idx(ppd); + struct rsm_rule_data rrd = { + .offset = rmt_start, + .pkt_type = RHF_RCV_TYPE_IB, + .pidx_mask = 1 << ppd->hw_pidx, + .field1_off = LRH_BTH_MATCH_OFFSET, + .mask1 = LRH_BTH_MASK, + .value1 = LRH_BTH_VALUE, + .field2_off = BTH_DESTQP_MATCH_OFFSET, + .mask2 = BTH_DESTQP_MASK, + .value2 = BTH_DESTQP_VALUE, + .index1_off = DETH_AIP_SQPN_SELECT_OFFSET + + ilog2(NUM_NETDEV_MAP_ENTRIES), + .index1_width = ilog2(NUM_NETDEV_MAP_ENTRIES), + .index2_off = DETH_AIP_SQPN_SELECT_OFFSET, + .index2_width = ilog2(NUM_NETDEV_MAP_ENTRIES) + }; + + hfi2_enable_rsm_rule(ppd, RSM_TYPE_AIP, &rrd); + } +} + +void hfi2_deinit_aip_rsm(struct hfi2_pportdata *ppd) +{ + /* only actually clear the rule if it's the last user asking to do so */ + if (atomic_fetch_add_unless(&ppd->ipoib_rsm_usr_num, -1, 0) == 1) { + mutex_lock(&hfi2_mutex); + clear_rsm_rule(ppd->dd, ppd->netdev_rsm_rule); + ppd->netdev_rsm_rule = -1; + mutex_unlock(&hfi2_mutex); + } +} + +/* + * RSM match rules for ports. + * + * RSM match bits are numbered little-endian by QWORD. Take this into account + * when deciding bit offsets. + */ +static const struct rsm_rule_data mad_response_rules[] = { + /* 9B Response MAD packets */ + { + .offset = 0, /* filled in */ + .pidx_mask = 0, /* filled in */ + .pkt_type = RHF_RCV_TYPE_IB, + .field1_off = 60, /* LRH.VL offset */ + .mask1 = 0x0f, /* 4 bits */ + .value1 = 0x0f, /* match LRH.VL=15 */ + .field2_off = 199, /* MAD.R offset */ + .mask2 = 0x01, /* 1 bit */ + .value2 = 0x01, /* match MAD.R=1 */ + .index1_off = 0, /* do not index off of offset */ + .index1_width = 0, + .index2_off = 0, + .index2_width = 0, + }, + /* 9B TRAP MAD packets */ + { + .offset = 0, /* filled in */ + .pidx_mask = 0, /* filled in */ + .pkt_type = RHF_RCV_TYPE_IB, + .field1_off = 60, /* LRH.VL offset */ + .mask1 = 0x0f, /* 4 bits */ + .value1 = 0x0f, /* match LRH.VL=15 */ + .field2_off = 192, /* MAD.Method offset */ + .mask2 = 0x7f, /* 7 bits */ + .value2 = 0x05, /* match MAD.Method=TRAP */ + .index1_off = 0, /* do not index off of offset */ + .index1_width = 0, + .index2_off = 0, + .index2_width = 0, + }, +}; + +/* + * 16B (bypass) Packets (STL Mgmt L4 Header version, aka L4_FM) + * -------------------- + * A 16B L4_FM type MAD packet starts with headers + * 16B LRH (16 bytes) + * Mgmt L4 (8 bytes) + * MAD + * + * Match vl15 in 16B.SC (5 bits) + * 16B.SC is bits 56:52 in the first QWORD. + * offset = 52 bit offset + * mask = 0x1f bits to check + * value = 0x0f value to compare + * + * Match MAD.R (1 bit) + * MAD.R is the 4th byte on the wire of the MAD header, bit 7. + * Preceding headers: 24 bytes = 192 bits + * Preceding bytes of MAD: 3 bytes = 24 bits + * Bit 7: = 7 bits + * Total: 192 + 24 + 7 = 223 + * + * offset = 223 bit offset + * mask = 0x01 bits to check + * value = 0x00 value to compare + */ +static const struct rsm_rule_data mad_action_rules[] = { + { + .offset = 0, /* filled in */ + .pidx_mask = 0, /* filled in */ + .pkt_type = RHF_RCV_TYPE_BYPASS, + .field1_off = 52, /* 16B.SC offset */ + .mask1 = 0x1f, /* 5 bits */ + .value1 = 0x0f, /* match 16B.SC=15 */ + .field2_off = 223, /* MAD.R offset */ + .mask2 = 0x01, /* 1 bit */ + .value2 = 0x00, /* match MAD.R=0 */ + .index1_off = 0, /* do not index off of offset */ + .index1_width = 0, + .index2_off = 0, + .index2_width = 0, + }, +}; + +/* + * Set up a rule for all entries in mmr_tbl to go to the target context. + */ +static int do_port_mapping(struct hfi2_pportdata *ppd, + struct rsm_map_table *rmt, + const struct rsm_rule_data *rrd_tbl, int rrd_len, + int target_ctxt, int rule_type) +{ + struct hfi2_devdata *dd = ppd->dd; + struct rsm_rule_data rrd; + int rmt_index; + int rule_index; + int row; + + /* check for room in RMT - need 1 entry */ + if (rmt->used >= dd->params->rsm_map_table_entries) { + ppd_dev_err(ppd, "%s: out of RMT entries\n", __func__); + return -ENOSPC; + } + /* allocate the RMT index */ + rmt_index = rmt->used++; + + /* update the RSM Map Table entry with the target context */ + set_rmt_entry(dd, rmt, rmt_index, target_ctxt); + + /* assign the rules */ + for (row = 0; row < rrd_len; row++) { + /* copy to a writable structure */ + rrd = rrd_tbl[row]; + + /* fill in variable fields */ + rrd.offset = rmt_index; /* lookup dest here... */ + rrd.pidx_mask = 1 << ppd->hw_pidx; /* ...on this port */ + + /* allocate a rule index */ + rule_index = alloc_rsm_rule(dd, rule_type); + if (rule_index < 0) + return rule_index; /* this is a -ERRNO */ + + /* set the rule registers */ + add_rsm_rule(dd, rule_index, &rrd); + } + + return 0; +} + +/* + * Add port-specific mappings to the RSM Map Table. + */ +static int init_port_mapping(struct hfi2_pportdata *ppd, + struct rsm_map_table *rmt) +{ + struct hfi2_portrsrcs *pr = &ppd->dd->rsrcs.ppr[ppd->hw_pidx]; + int ret; + + /* no port mapping for WFR */ + if (ppd->dd->params->chip_type == CHIP_WFR) + return 0; + + /* + * Redirect MAD responses and traps to the driver. Use this port's + * receive control context. + */ + ret = do_port_mapping(ppd, rmt, mad_response_rules, + ARRAY_SIZE(mad_response_rules), + pr->rcv_context_base + HFI2_CTRL_CTXT, + RSM_TYPE_MAD_RSP); + if (ret) + return ret; + + /* + * Redirect MAD actions to the cport. Cport owns the same receive + * context as the port index. + */ + ret = do_port_mapping(ppd, rmt, mad_action_rules, + ARRAY_SIZE(mad_action_rules), ppd->hw_pidx, + RSM_TYPE_MAD_ACTION); + return ret; +} + +/* + * This might be called on behalf of a VF to setup RSM rules and any + * associated RMT entries. This means it is no longer called only once + * at driver load. + * + * 'dr' is NULL when called for PF0 init. + */ +int hfi2_init_rxe_rsm(struct hfi2_devdata *dd, struct hfi2_devrsrcs *dr) +{ + struct rsm_map_table *rmt; + int i; + int ret; + + if (dr) { + /* working on behalf of VF */ + struct hfi2_pportdata *ppd; + u16 rc0, rcn; + + for (i = 0; i < dd->num_pports; ++i) { + ppd = &dd->pport[i]; + if (ppd->qos_shift != 1) { + ppd_dev_err(ppd, + "QOS not supported for SRIOV\n"); + return -EINVAL; + } + /* map our QPNs to port's kernel contexts (excl. HFI2_CTRL_CTXT) */ + rc0 = dr->ppr[i].rcv_context_base + FIRST_KERNEL_KCTXT; + rcn = rc0 + dr->ppr[i].n_krcv_queues - + FIRST_KERNEL_KCTXT - 1; + init_qpmap_table_range( + ppd, + dr->c.first_rcv_context, /* our QPN range */ + dr->c.last_rcv_context, rc0, rcn); + } + dd_dev_warn(dd, "SRIOV: additional RSM/RMT setup for SI %d\n", + dr->si_idx); + return 0; + } + /* + * CSRs and determine rmt->used. After chip reset, CSRs are all + * zero (a valid ctxt number) but we use 0xff to mark unused + * entries after first pass (complete_rsm_map_table()). + */ + rmt = alloc_rsm_map_table(dd); + if (!rmt) + return -ENOMEM; + + /* + * need to be done differently. For example, MAD responses + * will probably require an array of target contexts indexed + * by something like QPN. + */ + + /* set up QOS, including the QPN map table */ + init_qos(dd, rmt); + for (i = 0; i < dd->num_pports; i++) { + ret = init_port_mapping(dd->pport + i, rmt); + if (ret) + goto done; + init_fecn_handling(dd->pport + i, rmt); + } + complete_rsm_map_table(dd, rmt); + /* reserve RMT entries for netdev */ + for (i = 0; i < dd->num_pports; i++) { + ret = hfi2_netdev_set_free_rmt_idx(&dd->pport[i], rmt); + if (ret < 0) + goto done; + } + ret = 0; +done: + kfree(rmt); + return ret; +} + +/* + * Adjust RcvIportCtrl.RcvWcb, the write coalescing boundary for RX_DMA. + * + * Use the module parameter as a starting point. Tune this setting to the + * largest size <= PCIe Max Payload Size (MPS). It is invalid to have + * RcvWcb > MPS. + * + * It is expected that PCIe MPS is not adjusted after this function is called, + * both internally and externally. + */ +static void adjust_wcb(struct hfi2_devdata *dd) +{ + int mps = pcie_get_mps(dd->pcidev); + int local_wcb = rcvwcb; + int top; + int def; + int pidx; + + /* WFR has a different default and range */ + if (dd->params->chip_type == CHIP_WFR) { + def = 0; + top = 2; + } else { + def = 3; + top = 3; + } + + /* force this instance of rcvwcb in the range 0..top, inclusive */ + if (local_wcb < 0 || local_wcb > top) + local_wcb = def; + /* reduce local_wcb until it is <= MPS */ + while (local_wcb != 0) { + int sz = 64 << local_wcb; /* convert to a size */ + + if (sz <= mps) + break; + local_wcb--; + } + + /* wcb is required to be the same on all ports */ + for (pidx = 0; pidx < dd->num_pports; pidx++) + set_wcb(&dd->pport[pidx], local_wcb); +} + +static int init_rxe(struct hfi2_devdata *dd) +{ + u64 val; + int i; + int ret; + + if (dd->is_vf) { + for (i = 0; i < dd->num_pports; i++) + dd->pport[i].qos_shift = 1; /* otherwise won't happen */ + return hfi2_vf2pf_init_rxe_rsm(dd); + } + + /* enable all receive errors */ + for (i = 0; i < dd->num_pports; i++) { + write_iport_csr(dd, i, dd->params->rcv_err_mask_reg, ~0ull); + if (dd->is_sriov) + write_iport_csr(dd, loopback_pidx_dd(dd, i), + dd->params->rcv_err_mask_reg, ~0ull); + } + + ret = hfi2_init_rxe_rsm(dd, NULL); + if (ret) + goto done; + + /* set the DMA receive coalesce size */ + adjust_wcb(dd); + + for (i = 0; i < dd->num_pports; i++) { + struct hfi2_portrsrcs *pr = &dd->rsrcs.ppr[i]; + + u64 control = pr->rcv_context_base + HFI2_CTRL_CTXT; + + /* set 16 bytes (4 DW) header available in header queue */ + /* set bypass context to the port control context */ + val = (4ull << RCV_BYPASS_HDR_SIZE_SHIFT) | control; + write_iport_csr(dd, i, dd->params->rcv_bypass_reg, val); + + write_iport_csr(dd, i, dd->params->rcv_multicast_reg, control); + if (dd->is_sriov) { + write_iport_csr(dd, loopback_pidx_dd(dd, i), + dd->params->rcv_bypass_reg, val); + write_iport_csr(dd, loopback_pidx_dd(dd, i), + dd->params->rcv_multicast_reg, control); + } + } + ret = 0; + +done: + return ret; +} + +void hfi2_init_other(struct hfi2_devdata *dd) +{ + /* enable all CCE errors */ + hfi2_write_csr(dd, CCE_ERR_MASK, ~0ull); + /* enable *some* Misc errors */ + hfi2_write_csr(dd, MISC_ERR_MASK, DRIVER_MISC_MASK); + /* enable all DC errors, except LCB */ + hfi2_write_csr(dd, DCC_ERR_FLG_EN, ~0ull); + hfi2_write_csr(dd, DC_DC8051_ERR_EN, ~0ull); +} + +/* + * Fill out the given AU table using the given CU. A CU is defined in terms + * AUs. The table is a an encoding: given the index, how many AUs does that + * represent? + * + * NOTE: Assumes that the register layout is the same for the + * local and remote tables. + */ +static void assign_cm_au_table(struct hfi2_pportdata *ppd, u32 cu, u32 csr0to3, + u32 csr4to7) +{ + struct hfi2_devdata *dd = ppd->dd; + int pidx = ppd->hw_pidx; + + if (dd->params->chip_type != CHIP_WFR) + return; + + write_eport_csr( + dd, pidx, csr0to3, + 0ull << SEND_CM_LOCAL_AU_TABLE0_TO3_LOCAL_AU_TABLE0_SHIFT | + 1ull << SEND_CM_LOCAL_AU_TABLE0_TO3_LOCAL_AU_TABLE1_SHIFT | + 2ull * cu + << SEND_CM_LOCAL_AU_TABLE0_TO3_LOCAL_AU_TABLE2_SHIFT | + 4ull * cu + << SEND_CM_LOCAL_AU_TABLE0_TO3_LOCAL_AU_TABLE3_SHIFT); + write_eport_csr( + dd, pidx, csr4to7, + 8ull * cu << SEND_CM_LOCAL_AU_TABLE4_TO7_LOCAL_AU_TABLE4_SHIFT | + 16ull * cu + << SEND_CM_LOCAL_AU_TABLE4_TO7_LOCAL_AU_TABLE5_SHIFT | + 32ull * cu + << SEND_CM_LOCAL_AU_TABLE4_TO7_LOCAL_AU_TABLE6_SHIFT | + 64ull * cu + << SEND_CM_LOCAL_AU_TABLE4_TO7_LOCAL_AU_TABLE7_SHIFT); +} + +static void assign_local_cm_au_table(struct hfi2_pportdata *ppd, u8 vcu) +{ + assign_cm_au_table(ppd, vcu_to_cu(vcu), + ppd->dd->params->send_cm_local_au_table0_to3_reg, + ppd->dd->params->send_cm_local_au_table4_to7_reg); +} + +void hfi2_assign_remote_cm_au_table(struct hfi2_pportdata *ppd, u8 vcu) +{ + assign_cm_au_table(ppd, vcu_to_cu(vcu), + ppd->dd->params->send_cm_remote_au_table0_to3_reg, + ppd->dd->params->send_cm_remote_au_table4_to7_reg); +} + +static void init_txe(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + int i; + + /* enable all PIO, SDMA, general, and Egress errors */ + hfi2_write_csr(dd, dd->params->send_pio_err_mask_reg, ~0ull); + hfi2_write_csr(dd, dd->params->send_dma_err_mask_reg, ~0ull); + hfi2_write_csr(dd, dd->params->csr_err_mask_reg, ~0ull); + if (!dd->is_vf) + for (i = 0; i < dd->num_pports; i++) { + write_eport_csr(dd, i, + dd->params->send_egress_err_mask_reg, + ~0ull); + if (dd->is_sriov) + write_eport_csr( + dd, loopback_pidx_dd(dd, i), + dd->params->send_egress_err_mask_reg, + ~0ull); + } + + /* enable all per-context and per-SDMA engine errors */ + for (i = dr->c.first_send_context; i < dr->c.last_send_context; i++) + write_sctxt_csr(dd, i, dd->params->send_ctxt_err_mask_reg, + ~0ull); + for (i = dr->first_sdma_engine; i < dr->last_sdma_engine; i++) + write_sdma_csr(dd, i, dd->params->send_dma_eng_err_mask_reg, + ~0ull); + + if (dd->params->chip_type == CHIP_WFR) { + /* set the local CU to AU mapping */ + for (i = 0; i < dd->num_pports; i++) + assign_local_cm_au_table(dd->pport + i, dd->vcu); + + /* + * Set reasonable default for Credit Return Timer + */ + for (i = 0; i < dd->num_pports; i++) { + write_eport_csr(dd, i, + dd->params->send_cm_timer_ctrl_reg, + HFI2_CREDIT_RETURN_RATE); + } + } +} + +int hfi2_set_ctxt_jkey(struct hfi2_devdata *dd, struct hfi2_ctxtdata *rcd, + u16 jkey) +{ + int pidx; + u16 hw_ctxt; + u64 reg; + + if (!rcd || !rcd->sc) + return -EINVAL; + + pidx = rcd->ppd->hw_pidx; + hw_ctxt = rcd->sc->hw_context; + reg = SEND_CTXT_CHECK_JOB_KEY_MASK_SMASK | /* mask is always 1's */ + ((jkey & SEND_CTXT_CHECK_JOB_KEY_VALUE_MASK) + << SEND_CTXT_CHECK_JOB_KEY_VALUE_SHIFT); + /* JOB_KEY_ALLOW_PERMISSIVE is not allowed by default */ + if (HFI2_CAP_KGET_MASK(rcd->flags, ALLOW_PERM_JKEY)) + reg |= SEND_CTXT_CHECK_JOB_KEY_ALLOW_PERMISSIVE_SMASK; + hfi2_priv_reg_op(dd, pidx, hw_ctxt | (rcd->ctxt << 16), rcd->sc->type, + SC_CHK_JKEY_OP, reg); + + return 0; +} + +int hfi2_clear_ctxt_jkey(struct hfi2_devdata *dd, struct hfi2_ctxtdata *rcd) +{ + int pidx; + u16 hw_ctxt; + + if (!rcd || !rcd->sc) + return -EINVAL; + + pidx = rcd->ppd->hw_pidx; + hw_ctxt = rcd->sc->hw_context; + hfi2_priv_reg_op(dd, pidx, hw_ctxt | (rcd->ctxt << 16), rcd->sc->type, + SC_CHK_JKEY_OP, 0); + + return 0; +} + +int hfi2_set_ctxt_pkey(struct hfi2_devdata *dd, struct hfi2_ctxtdata *rcd, + u16 pkey) +{ + int pidx; + u16 hw_ctxt; + u64 reg; + + if (!rcd || !rcd->sc) + return -EINVAL; + + pidx = rcd->ppd->hw_pidx; + hw_ctxt = rcd->sc->hw_context; + reg = ((u64)pkey & SEND_CTXT_CHECK_PARTITION_KEY_VALUE_MASK) + << SEND_CTXT_CHECK_PARTITION_KEY_VALUE_SHIFT; + hfi2_priv_reg_op(dd, pidx, hw_ctxt, rcd->sc->type, SC_CHK_PKEY_OP, reg); + + return 0; +} + +int hfi2_clear_ctxt_pkey(struct hfi2_devdata *dd, struct hfi2_ctxtdata *ctxt) +{ + int pidx; + u16 hw_ctxt; + + if (!ctxt || !ctxt->sc) + return -EINVAL; + + pidx = ctxt->ppd->hw_pidx; + hw_ctxt = ctxt->sc->hw_context; + hfi2_priv_reg_op(dd, pidx, hw_ctxt, ctxt->sc->type, SC_CHK_PKEY_OP, 0); + + return 0; +} + +/* + * Start doing the clean up the chip. Our clean up happens in multiple + * stages and this is just the first. + */ +void hfi2_start_cleanup(struct hfi2_devdata *dd) +{ + free_cntrs(dd); + hfi2_finish_chip_resources(dd); +} + +#define HFI_BASE_GUID(dev) ((dev)->base_guid & ~(1ULL << GUID_HFI_INDEX_SHIFT)) + +/* + * Information can be shared between the two HFIs on the same ASIC + * in the same OS. This function finds the peer device and sets + * up a shared structure. + */ +static int init_asic_data(struct hfi2_devdata *dd) +{ + unsigned long index; + struct hfi2_devdata *peer; + struct hfi2_asic_data *asic_data; + int ret = 0; + + /* pre-allocate the asic structure in case we are the first device */ + asic_data = kzalloc_obj(dd->asic_data, GFP_KERNEL); + if (!asic_data) + return -ENOMEM; + + xa_lock_irq(&hfi2_dev_table); + /* Find our peer device */ + xa_for_each(&hfi2_dev_table, index, peer) { + if ((HFI_BASE_GUID(dd) == HFI_BASE_GUID(peer)) && + dd->unit != peer->unit) + break; + } + + if (peer) { + /* use already allocated structure */ + dd->asic_data = peer->asic_data; + kfree(asic_data); + } else { + dd->asic_data = asic_data; + mutex_init(&dd->asic_data->asic_resource_mutex); + } + dd->asic_data->dds[dd->hfi2_id] = dd; /* self back-pointer */ + xa_unlock_irq(&hfi2_dev_table); + + /* first one through - set up i2c devices */ + if (!peer) + ret = hfi2_set_up_i2c(dd, dd->asic_data); + + return ret; +} + +/* + * Set dd->boardname. Use a generic name if a name is not returned from + * EFI variable space. + * + * Return 0 on success, -ENOMEM if space could not be allocated. + */ +static int obtain_boardname(struct hfi2_devdata *dd) +{ + unsigned long size; + int ret; + + ret = hfi2_read_hfi2_efi_var(dd, "description", &size, + (void **)&dd->boardname); + if (ret) { + dd_dev_info(dd, "Board description not found\n"); + /* use generic description */ + dd->boardname = + kstrdup(dd->params->generic_boardname, GFP_KERNEL); + if (!dd->boardname) + return -ENOMEM; + } + return 0; +} + +/* + * Check the interrupt registers to make sure that they are mapped correctly. + * It is intended to help user identify any mismapping by VMM when the driver + * is running in a VM. This function should only be called before interrupt + * is set up properly. + * + * Return 0 on success, -EINVAL on failure. + */ +static int check_int_registers(struct hfi2_devdata *dd) +{ + u64 reg; + u64 all_bits = ~(u64)0; + u64 mask; + u32 cce_int_mask_reg = dd->params->cce_int_mask_reg; + u32 cce_int_clear_reg = dd->params->cce_int_clear_reg; + u32 cce_int_status_reg = dd->params->cce_int_status_reg; + + /* Clear CceIntMask[0] to avoid raising any interrupts */ + mask = hfi2_read_csr(dd, cce_int_mask_reg); + hfi2_write_csr(dd, cce_int_mask_reg, 0ull); + reg = hfi2_read_csr(dd, cce_int_mask_reg); + if (reg) + goto err_exit; + + /* Clear all interrupt status bits */ + hfi2_write_csr(dd, cce_int_clear_reg, all_bits); + reg = hfi2_read_csr(dd, cce_int_status_reg); + if (reg) + goto err_exit; + + /* Set all interrupt status bits */ + hfi2_write_csr(dd, dd->params->cce_int_force_reg, all_bits); + reg = hfi2_read_csr(dd, cce_int_status_reg); + if (reg != all_bits) + goto err_exit; + + /* Restore the interrupt mask */ + hfi2_write_csr(dd, cce_int_clear_reg, all_bits); + hfi2_write_csr(dd, cce_int_mask_reg, mask); + + return 0; +err_exit: + hfi2_write_csr(dd, cce_int_mask_reg, mask); + dd_dev_err(dd, "Interrupt registers not properly mapped by VMM\n"); + return -EINVAL; +} + +int hfi2_wfr_find_used_resources(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + + /* set resource allocation start values */ + dr->pfunit = dd->unit; + dr->c.first_rcvarray_entry = 0; + dr->c.last_rcvarray_entry = chip_rcv_array_count(dd); + dr->c.first_pio_block = 1; /* do not use block 0, HAS entry 291585 */ + dr->c.last_pio_block = chip_pio_mem_size(dd) / PIO_BLOCK_SIZE; + dr->c.first_rcv_context = 0; + dr->c.last_rcv_context = chip_rcv_contexts(dd); + dr->c.first_send_context = 0; + dr->c.last_send_context = chip_send_contexts(dd); + dd->rsm_rule_init = true; + + return 0; +} + +/* early WFR specific chip init */ +int hfi2_wfr_early_per_chip_init(struct hfi2_devdata *dd) +{ + int ret; + + /* set up shared ASIC data with peer device */ + ret = init_asic_data(dd); + if (ret) + return ret; + + /* obtain chip sizes, reset chip CSRs */ + ret = init_chip(dd); + if (ret) + return ret; + + /* read in the PCIe link speed information */ + ret = hfi2_pcie_speeds(dd); + if (ret) + return ret; + + /* call before get_platform_config(), after hfi2_init_chip_resources() */ + ret = hfi2_eprom_init(dd); + if (ret) + return ret; + + /* Needs to be called before hfi2_firmware_init */ + get_platform_config(&dd->pport[HFI2_PORT_IDX]); + + /* read in firmware */ + ret = hfi2_firmware_init(dd); + if (ret) + return ret; + + /* + * In general, the PCIe Gen3 transition must occur after the + * chip has been idled (so it won't initiate any PCIe transactions + * e.g. an interrupt) and before the driver changes any registers + * (the transition will reset the registers). + * + * In particular, place this call after: + * - init_chip() - the chip will not initiate any PCIe transactions + * - hfi2_pcie_speeds() - reads the current link speed + * - hfi2_firmware_init() - the needed firmware is ready to be + * downloaded + */ + ret = hfi2_do_pcie_gen3_transition(dd); + if (ret) + return ret; + + /* + * This should probably occur in hfi2_pcie_init(), but historically + * occurs after the hfi2_do_pcie_gen3_transition() code. + */ + hfi2_tune_pcie_caps(dd); + + /* start setting dd values and adjusting CSRs */ + hfi2_init_early_variables(dd); + + write_uninitialized_csrs_and_memories(dd); + + hfi2_parse_platform_config(&dd->pport[HFI2_PORT_IDX]); + + return 0; +} + +int hfi2_wfr_mid_per_chip_init(struct hfi2_devdata *dd) +{ + return 0; +} + +int hfi2_wfr_late_per_chip_init(struct hfi2_devdata *dd) +{ + /* set up LCB access - must be after set_up_interrupts() */ + init_lcb_access(dd); + + return hfi2_load_firmware( + dd); /* asymmetric with hfi2_dispose_firmware() */ +} + +/** + * hfi2_init_dd() - Initialize most of the dd structure. + * @dd: the dd device + * + * This is global, and is called directly at init to set up the + * chip-specific function pointers for later use. + */ +int hfi2_init_dd(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + struct pci_dev *pdev = dd->pcidev; + struct hfi2_pportdata *ppd; + u64 reg; + int i, ret; + static const char *const inames[] = { + /* implementation names */ + "RTL silicon", "RTL VCS simulation", "RTL FPGA emulation", + "Functional simulator" + }; + struct pci_dev *parent = pdev->bus->self; + + /* + * Do remaining PCIe setup. Error messaging is done by the callee. + * On return, the BAR is mapped and register access is enabled. + */ + ret = hfi2_pcie_ddinit(dd, pdev); + if (ret < 0) + goto bail; + + /* do this at earliest opportunity - CSRs accessible */ + dd->rctxt_mask = (1 << fls(chip_rcv_contexts(dd))) - 1; + dd->sctxt_mask = (1 << fls(chip_send_contexts(dd))) - 1; + + ret = hfi2_vf2pf_early_init(dd); + if (ret) + goto bail_cleanup; + /* + * Only VFs can/must init VF2PF this early. + * The PF must wait until CPORT f/w has reset all + * resources in hfi2_start_cport(). + */ + if (dd->is_vf) { + /* This must also set the SI */ + ret = hfi2_vf2pf_init(dd); + if (ret) + goto bail_cleanup; + } + + /* + * must be done before dd->params->find_used_resources() + * but after hfi2_pcie_ddinit() (BARs enabled). + * After this call, dd->rsrcs should have basic data needed + * to initialize the driver resources. + */ + ret = hfi2_sriov_set_cfg(dd); + if (ret) + goto bail; + + if (hfi2_num_vls < HFI2_MIN_VLS_SUPPORTED || + hfi2_num_vls > HFI2_MAX_VLS_SUPPORTED) { + dd_dev_err(dd, "Invalid hfi2_num_vls %u, using %u VLs\n", + hfi2_num_vls, HFI2_MAX_VLS_SUPPORTED); + hfi2_num_vls = HFI2_MAX_VLS_SUPPORTED; + } + + /* + * Decide on number of SDMA engines to use based on hardware + * availability, number of VLs, and module parameter. + */ + if (HFI2_CAP_IS_KSET(SDMA)) { + u32 sdma_engines = chip_sdma_engines(dd); + + /* insure hfi2_num_vls isn't larger than number of sdma engines */ + if (hfi2_num_vls > sdma_engines) { + dd_dev_err(dd, + "hfi2_num_vls %u too large, using %u VLs\n", + hfi2_num_vls, sdma_engines); + hfi2_num_vls = sdma_engines; + } + + /* + * should revisit whether this is supported at all. + * It is not being factored into SRIOV SDMA assignment. + */ + if (hfi2_mod_num_sdma && + /* can't exceed chip support */ + hfi2_mod_num_sdma <= sdma_engines && + /* count must be >= vls */ + hfi2_mod_num_sdma >= hfi2_num_vls) + sdma_engines = hfi2_mod_num_sdma; + + dd->num_sdma = sdma_engines; + if (dr->num_vfs) { + int num_sde = + dr->last_sdma_engine - dr->first_sdma_engine; + + /* resources already setup by hfi2_sriov_set_cfg() */ + if (hfi2_num_vls > num_sde) { + dd_dev_err( + dd, + "SI%d: hfi2_num_vls %u too large, using %u VLs\n", + dr->si_idx, hfi2_num_vls, num_sde); + hfi2_num_vls = num_sde; + } + } else { + dr->first_sdma_engine = 0; + dr->last_sdma_engine = dd->num_sdma; + } + } else { + HFI2_CAP_CLEAR(SDMA_AHG); + dd->num_sdma = 0; + } + + ppd = dd->pport; + for (i = 0; i < dd->num_pports; i++, ppd++) { + int vl; + /* init common fields */ + hfi2_init_pportdata(pdev, ppd, dd, i, i + 1); + /* DC supports 4 link widths */ + ppd->link_width_supported = + OPA_LINK_WIDTH_1X | OPA_LINK_WIDTH_2X | + OPA_LINK_WIDTH_3X | OPA_LINK_WIDTH_4X; + ppd->link_width_downgrade_supported = ppd->link_width_supported; + /* start out enabling only 4X */ + ppd->link_width_enabled = OPA_LINK_WIDTH_4X; + ppd->link_width_downgrade_enabled = + ppd->link_width_downgrade_supported; + /* link width active is 0 when link is down */ + /* link width downgrade active is 0 when link is down */ + + ppd->vls_supported = hfi2_num_vls; + ppd->vls_operational = ppd->vls_supported; + /* Set the default MTU. */ + for (vl = 0; vl < hfi2_num_vls; vl++) + ppd->vld[vl].mtu = hfi2_max_mtu; + ppd->vld[15].mtu = MAX_MAD_PACKET; + + /* + * Set the initial values to reasonable default, will be set + * for real when link is up. + */ + ppd->overrun_threshold = 0x4; + ppd->phy_error_threshold = 0xf; + ppd->port_crc_mode_enabled = hfi2_link_crc_mask; + /* initialize supported LTP CRC mode */ + ppd->port_ltp_crc_mode = cap_to_port_ltp(hfi2_link_crc_mask) + << 8; + /* initialize enabled LTP CRC mode */ + ppd->port_ltp_crc_mode |= cap_to_port_ltp(hfi2_link_crc_mask) + << 4; + /* start in offline */ + ppd->host_link_state = HLS_DN_OFFLINE; + init_vl_arb_caches(ppd); + + /* speeds the hardware can support */ + ppd->link_speed_supported = dd->params->link_speed_supported; + /* speeds allowed to run at */ + ppd->link_speed_enabled = ppd->link_speed_supported; + /* give a reasonable active value, will be set on link up */ + ppd->link_speed_active = dd->params->link_speed_active; + } + + /* Save PCI space registers to rewrite after device reset */ + ret = hfi2_save_pci_variables(dd); + if (ret < 0) + goto bail_cleanup; + + dd->majrev = (dd->revision >> CCE_REVISION_CHIP_REV_MAJOR_SHIFT) & + CCE_REVISION_CHIP_REV_MAJOR_MASK; + dd->minrev = (dd->revision >> CCE_REVISION_CHIP_REV_MINOR_SHIFT) & + CCE_REVISION_CHIP_REV_MINOR_MASK; + + /* + * Check interrupt registers mapping if the driver has no access to + * the upstream component. In this case, it is likely that the driver + * is running in a VM. + */ + if (!parent) { + ret = check_int_registers(dd); + if (ret) + goto bail_cleanup; + } + + /* + * Obtain the hardware ID - NOT related to unit, which is a + * software enumeration. VFs can't access CSR directly and + * already got this from PF0 via hfi2_vf2pf_get_config(). + */ + if (!dd->is_vf) { + reg = hfi2_read_csr(dd, CCE_REVISION2); + dd->hfi2_id = (reg >> CCE_REVISION2_HFI_ID_SHIFT) & + CCE_REVISION2_HFI_ID_MASK; + /* the variable size will remove unwanted bits */ + dd->icode = reg >> CCE_REVISION2_IMPL_CODE_SHIFT; + dd->irev = reg >> CCE_REVISION2_IMPL_REVISION_SHIFT; + } + dd_dev_info(dd, "Implementation: %s, revision 0x%x\n", + dd->icode < ARRAY_SIZE(inames) ? inames[dd->icode] : + "unknown", + (int)dd->irev); + + /* + * Convert the ns parameter to the 64 * cclocks used in the CSR. + * Limit the max if larger than the field holds. If timeout is + * non-zero, then the calculated field will be at least 1. + * + * Must be after icode is set up - the cclock rate depends + * on knowing the hardware being used. + */ + dd->rcv_intr_timeout_csr = + hfi2_ns_to_cclock(dd, hfi2_rcv_intr_timeout) / 64; + if (dd->rcv_intr_timeout_csr > RCV_AVAIL_TIME_OUT_TIME_OUT_RELOAD_MASK) + dd->rcv_intr_timeout_csr = + RCV_AVAIL_TIME_OUT_TIME_OUT_RELOAD_MASK; + else if (dd->rcv_intr_timeout_csr == 0 && hfi2_rcv_intr_timeout) + dd->rcv_intr_timeout_csr = 1; + + /* find resources already consumed */ + ret = dd->params->find_used_resources(dd); + if (ret) + goto bail_cleanup; + + ret = set_up_context_variables(dd); + if (ret) + goto bail_cleanup; + + ret = dd->params->early_per_chip_init(dd); + if (ret) + goto bail_cleanup; + + ret = set_up_interrupts(dd); + if (ret) + goto bail_cleanup; + + /* + * This does a STOP which will reset many things, + * particularly the PF contexts needed for VF2PF. + * The PF must not initialize VF2PF until after this. + */ + ret = hfi2_start_cport(dd); + if (ret) + goto bail_clean_early_intr; + if (!dd->is_vf) { + /* The PF can safely init resources now */ + ret = hfi2_vf2pf_init(dd); + if (ret) + goto bail_clean_early_intr; + ret = hfi2_vf2pf_init_irq(dd); + if (ret) + goto bail_clean_early_intr; + } + + /* needs to be done before we look for the peer device */ + dd->params->hfi2_read_guid(dd); + dd_dev_info(dd, "GUID %llx", (unsigned long long)dd->base_guid); + + ret = dd->params->mid_per_chip_init(dd); + if (ret) + goto bail_clean_early_intr; + + ret = obtain_boardname(dd); + if (ret) + goto bail_clean_early_intr; + + snprintf(dd->boardversion, BOARD_VERS_MAX, + "ChipABI %u.%u, ChipRev %u.%u, SW Compat %llu\n", + HFI2_CHIP_VERS_MAJ, HFI2_CHIP_VERS_MIN, (u32)dd->majrev, + (u32)dd->minrev, + (dd->revision >> CCE_REVISION_SW_SHIFT) & + CCE_REVISION_SW_MASK); + + /* alloc AIP rx data */ + ret = hfi2_alloc_rx(dd); + if (ret) + goto bail_free_boardname; + + /* set initial RXE CSRs */ + ret = init_rxe(dd); + if (ret) + goto bail_free_rx; + + /* set initial TXE CSRs */ + init_txe(dd); + /* set initial non-RXE, non-TXE CSRs */ + dd->params->hfi2_init_other(dd); + /* set up KDETH QP prefix in both RX and TX CSRs */ + hfi2_init_kdeth_qp(dd); + + /* send contexts must be set up before receive contexts */ + ret = hfi2_init_send_contexts(dd); + if (ret) + goto bail_free_rx; + + ret = hfi2_create_kctxts(dd); + if (ret) + goto bail_free_rx; + + /* per-vl send context and sdma init */ + for (i = 0; i < dd->num_pports; ++i) { + ret = hfi2_init_pervl_scs(dd->pport + i); + if (ret) + goto bail_free_rx; + } + ret = hfi2_sdma_init(dd); + if (ret) + goto bail_free_rx; + + /* use contexts created by hfi2_create_kctxts */ + ret = late_set_up_interrupts(dd); + if (ret) + goto bail_free_rx; + + ret = hfi2_comp_vectors_set_up(dd); + if (ret) + goto bail_clear_comp_vectors; + + ret = dd->params->late_per_chip_init(dd); + if (ret) + goto bail_clear_comp_vectors; + + /* + * Serial number is created from the base guid: + * [27:24] = base guid [38:35] + * [23: 0] = base guid [23: 0] + */ + snprintf(dd->serial, SERIAL_MAX, "0x%08llx\n", + (dd->base_guid & 0xFFFFFF) | + ((dd->base_guid >> 11) & 0xF000000)); + + dd->oui1 = dd->base_guid >> 56 & 0xFF; + dd->oui2 = dd->base_guid >> 48 & 0xFF; + dd->oui3 = dd->base_guid >> 40 & 0xFF; + + thermal_init(dd); + + ret = init_cntrs(dd); + if (ret) + goto bail_clear_comp_vectors; + + init_completion(&dd->user_comp); + + /* The user refcount starts with one to inidicate an active device */ + refcount_set(&dd->user_refcount, 1); + + goto bail; + +bail_clear_comp_vectors: + hfi2_comp_vectors_clean_up(dd); +bail_free_rx: + hfi2_sriov_free_rsrcs(dd, &dd->rsrcs); + hfi2_free_rx(dd); +bail_free_boardname: + kfree(dd->boardname); + dd->boardname = NULL; +bail_clean_early_intr: + hfi2_msix_clean_up_interrupts(dd); +bail_cleanup: + hfi2_pcie_ddcleanup(dd); +bail: + return ret; +} + +static u16 delay_cycles(struct hfi2_pportdata *ppd, u32 desired_egress_rate, + u32 dw_len) +{ + u32 delta_cycles; + u32 current_egress_rate = ppd->current_egress_rate; + /* rates here are in units of 10^6 bits/sec */ + + if (desired_egress_rate == -1) + return 0; /* shouldn't happen */ + + if (desired_egress_rate >= current_egress_rate) + return 0; /* we can't help go faster, only slower */ + + delta_cycles = egress_cycles(dw_len * 4, desired_egress_rate) - + egress_cycles(dw_len * 4, current_egress_rate); + + return (u16)delta_cycles; +} + +/** + * hfi2_wfr_create_pbc - build a pbc for transmission + * @ppd: info of physical Hfi port + * @hfi2_loopback: ignored for WFR + * @flags: special case flags or-ed in built pbc + * @srate_mbs: static rate + * @vl: vl + * @dw_len: dword length (header words + data words + pbc words) + * @l2: L2 header field - determines type + * @dlid: destination LID - unused + * @sctxt: send context number - unused + * + * Create a PBC with the given flags, rate, VL, and length. + * + * NOTE: The PBC created will not insert any HCRC - all callers but one are + * for verbs, which does not use this PSM feature. The lone other caller + * is for the diagnostic interface which calls this if the user does not + * supply their own PBC. + */ +u64 hfi2_wfr_create_pbc(struct hfi2_pportdata *ppd, bool hfi2_loopback, + u64 flags, int srate_mbs, u32 vl, u32 dw_len, u32 l2, + u32 dlid, u32 sctxt) +{ + u64 pbc, delay = 0; + + if (unlikely(srate_mbs)) + delay = delay_cycles(ppd, srate_mbs, dw_len); + + /* on WFR, non-9B are always bypass */ + if (l2 != PBC_L2_9B) + flags |= PBC_PACKET_BYPASS | PBC_INSERT_BYPASS_ICRC; + + pbc = flags | (delay << PBC_STATIC_RATE_CONTROL_COUNT_SHIFT) | + ((u64)PBC_IHCRC_NONE << PBC_INSERT_HCRC_SHIFT) | + (vl & PBC_VL_MASK) << PBC_VL_SHIFT | + (dw_len & PBC_LENGTH_DWS_MASK) << PBC_LENGTH_DWS_SHIFT; + + return pbc; +} + +#define SBUS_THERMAL 0x4f +#define SBUS_THERM_MONITOR_MODE 0x1 + +#define THERM_FAILURE(dev, ret, reason) \ + dd_dev_err((dev), "Thermal sensor initialization failed: %s (%d)\n", \ + (reason), (ret)) + +/* + * Initialize the thermal sensor. + * + * After initialization, enable polling of thermal sensor through + * SBus interface. In order for this to work, the SBus Master + * firmware has to be loaded due to the fact that the HW polling + * logic uses SBus interrupts, which are not supported with + * default firmware. Otherwise, no data will be returned through + * the ASIC_STS_THERM CSR. + */ +static int thermal_init(struct hfi2_devdata *dd) +{ + int ret = 0; + + if (dd->params->chip_type != CHIP_WFR) + return ret; + if (dd->icode != ICODE_RTL_SILICON || + dd->params->chip_type != CHIP_WFR || + hfi2_check_chip_resource(dd, CR_THERM_INIT, NULL)) + return ret; + + ret = hfi2_acquire_chip_resource(dd, CR_SBUS, SBUS_TIMEOUT); + if (ret) { + THERM_FAILURE(dd, ret, "Acquire SBus"); + return ret; + } + + dd_dev_info(dd, "Initializing thermal sensor\n"); + /* Disable polling of thermal readings */ + hfi2_write_csr(dd, ASIC_CFG_THERM_POLL_EN, 0x0); + msleep(100); + /* Thermal Sensor Initialization */ + /* Step 1: Reset the Thermal SBus Receiver */ + ret = hfi2_sbus_request_slow(dd, SBUS_THERMAL, 0x0, RESET_SBUS_RECEIVER, + 0); + if (ret) { + THERM_FAILURE(dd, ret, "Bus Reset"); + goto done; + } + /* Step 2: Set Reset bit in Thermal block */ + ret = hfi2_sbus_request_slow(dd, SBUS_THERMAL, 0x0, WRITE_SBUS_RECEIVER, + 0x1); + if (ret) { + THERM_FAILURE(dd, ret, "Therm Block Reset"); + goto done; + } + /* Step 3: Write clock divider value (100MHz -> 2MHz) */ + ret = hfi2_sbus_request_slow(dd, SBUS_THERMAL, 0x1, WRITE_SBUS_RECEIVER, + 0x32); + if (ret) { + THERM_FAILURE(dd, ret, "Write Clock Div"); + goto done; + } + /* Step 4: Select temperature mode */ + ret = hfi2_sbus_request_slow(dd, SBUS_THERMAL, 0x3, WRITE_SBUS_RECEIVER, + SBUS_THERM_MONITOR_MODE); + if (ret) { + THERM_FAILURE(dd, ret, "Write Mode Sel"); + goto done; + } + /* Step 5: De-assert block reset and start conversion */ + ret = hfi2_sbus_request_slow(dd, SBUS_THERMAL, 0x0, WRITE_SBUS_RECEIVER, + 0x2); + if (ret) { + THERM_FAILURE(dd, ret, "Write Reset Deassert"); + goto done; + } + /* Step 5.1: Wait for first conversion (21.5ms per spec) */ + msleep(22); + + /* Enable polling of thermal readings */ + hfi2_write_csr(dd, ASIC_CFG_THERM_POLL_EN, 0x1); + + /* Set initialized flag */ + ret = hfi2_acquire_chip_resource(dd, CR_THERM_INIT, 0); + if (ret) + THERM_FAILURE(dd, ret, "Unable to set thermal init flag"); + +done: + hfi2_release_chip_resource(dd, CR_SBUS); + return ret; +} + +void hfi2_handle_temp_err(struct hfi2_devdata *dd) +{ + struct hfi2_pportdata *ppd; + int pidx; + /* + * Thermal Critical Interrupt + * Put the device into forced freeze mode, take link down to + * offline, and put DC into reset. + */ + dd_dev_emerg( + dd, + "Critical temperature reached! Forcing device into freeze mode!\n"); + dd->flags |= HFI2_FORCED_FREEZE; + + hfi2_start_freeze_handling(dd, FREEZE_SELF | FREEZE_ABORT); + for (pidx = 0; pidx < dd->num_pports; ++pidx) { + ppd = dd->pport + pidx; + + /* + * Shut DC down as much and as quickly as possible. + * + * Step 1: Iterate over the ports and take the links down to OFFLINE. + * This will cause the 8051 to put the Serdes in reset. However, + * we don't want to go through the entire link state machine since + * we want to shutdown ASAP. Furthermore, this is not a graceful + * shutdown but rather an attempt to save the chip. + * Code below is almost the same as quiet_serdes() but avoids + * all the extra work and the sleeps. + */ + ppd->driver_link_ready = 0; + ppd->link_enabled = 0; + } + + set_physical_link_state(dd, (OPA_LINKDOWN_REASON_SMA_DISABLED << 8) | + PLS_OFFLINE); + /* + * Step 2: Shutdown LCB and 8051 + * After shutdown, do not restore DC_CFG_RESET value. + */ + dc_shutdown(dd); +} diff --git a/drivers/infiniband/hw/hfi2/chip_gen.c b/drivers/infiniband/hw/hfi2/chip_gen.c new file mode 100644 index 000000000000..9273ba66a8c8 --- /dev/null +++ b/drivers/infiniband/hw/hfi2/chip_gen.c @@ -0,0 +1,1151 @@ +// SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause +/* + * Copyright(c) 2025-2026 Cornelis Networks, Inc. + * + * Generalized (parameterized) chip specific functions and variables. + */ + +#include "hfi2.h" +#include "chip_gen.h" +#include "chip_jkr.h" +#include "cport_traps.h" +#include "vf2pf.h" +#include "sriov.h" + +#undef DEBUG_CPORT_TRAP + +#define SC(name) SEND_CTXT_##name + +/* + * Control the port LED state. Cancel with hfi2_gen_shutdown_led_override(). + */ +void hfi2_gen_setextled(struct hfi2_pportdata *ppd, u32 on) +{ + ppd_dev_warn(ppd, "%s: on %d, not implemented\n", __func__, on); +} + +/* + * Make the port LED blink in pattern. Parameters timeon and timeoff are + * in milliseconds. Cancel with hfi2_gen_shutdown_led_override(). + */ +void hfi2_gen_start_led_override(struct hfi2_pportdata *ppd, unsigned int timeon, + unsigned int timeoff) +{ + ppd_dev_warn(ppd, "%s: not implemented\n", __func__); + + /* used by the subnet manager to know if it set beaconing */ + atomic_set(&ppd->led_override_timer_active, 1); + /* ensure the atomic_set is visible to all CPUs */ + smp_wmb(); +} + +/* + * Return to normal LED operation. This cancels overrides started with + * hfi2_gen_setextled() or hfi2_gen_start_led_override(). + */ +void hfi2_gen_shutdown_led_override(struct hfi2_pportdata *ppd) +{ + ppd_dev_warn(ppd, "%s: not implemented\n", __func__); + + /* used by the subnet manager to know if it set beaconing */ + atomic_set(&ppd->led_override_timer_active, 0); + /* ensure the atomic_set is visible to all CPUs */ + smp_wmb(); +} + +void hfi2_gen_read_guid(struct hfi2_devdata *dd) +{ + dd_dev_warn(dd, "%s: not implemented\n", __func__); + + dd->base_guid = 0xabcd; +} + +int hfi2_gen_late_per_chip_init(struct hfi2_devdata *dd) +{ + return 0; +} + +void hfi2_gen_start_port(struct hfi2_pportdata *ppd) +{ + struct hfi2_devdata *dd = ppd->dd; + u64 guid; + + /* + * Derive the port GUID from the device base GUID if not already set. + * Per OPA spec, bits 34:32 encode the port number (1-7). + */ + guid = ppd->guids[HFI2_PORT_GUID_INDEX]; + if (!guid) { + /* OPA spec says bits 34:32 are port number, 1-7 */ + if (dd->base_guid) + guid = (dd->base_guid & ~(7ULL << 32)) | + ((u64)ppd->port << 32); + ppd->guids[HFI2_PORT_GUID_INDEX] = guid; + pr_warn("%s: ppd->guids[HFI2_PORT_GUID_INDEX] = 0x%llx", + __func__, guid); + } +} + +void hfi2_gen_stop_port(struct hfi2_pportdata *ppd) +{ + ppd_dev_warn(ppd, "%s: pidx %d, not implemented\n", __func__, + ppd->hw_pidx); +} + +void hfi2_gen_set_port_max_mtu(struct hfi2_pportdata *ppd, u32 maxvlmtu) +{ + ppd_dev_warn(ppd, "%s: pidx %d, not implemented\n", __func__, + ppd->hw_pidx); +} + +u64 hfi2_gen_create_pbc_pidx(u8 pidx, u64 flags, int srate_mbs, u32 vl, u32 dw_len, + u32 l2, u32 dlid, u32 sctxt) +{ + /* always add ICRC for non 9B packets */ + if (l2 != PBC_L2_9B) + flags |= PBC_INSERT_BYPASS_ICRC; /* AKA PbcInsertNon9bIcrc */ + + return (u64)sctxt << PBC_SEND_CTXT_SHIFT | (u64)dlid << PBC_DLID_SHIFT | + /* lower 32 bits */ + flags | PBC_IHCRC_NONE << PBC_INSERT_HCRC_SHIFT | + l2 << PBC_L2_TYPE_SHIFT | pidx << PBC_PORT_IDX_SHIFT | + (vl & PBC_VL_MASK) << PBC_VL_SHIFT | + (dw_len & PBC_LENGTH_DWS_MASK) << PBC_LENGTH_DWS_SHIFT; +} + +/** + * hfi2_gen_create_pbc - build a pbc for transmission + * @ppd: info of physical Hfi port + * @hfi2_loopback: whether to use hfi2_loopback port + * @flags: special case flags or-ed in built pbc + * @srate_mbs: static rate - unused + * @vl: vl + * @dw_len: dword length (header words + data words + pbc words) + * @l2: L2 header field - determines type + * @dlid: destination LID + * @sctxt: send context number + * + * Create a PBC with the given flags, rate, VL, and length. + * + * NOTE: The PBC created will not insert any HCRC. + */ +u64 hfi2_gen_create_pbc(struct hfi2_pportdata *ppd, bool hfi2_loopback, u64 flags, + int srate_mbs, u32 vl, u32 dw_len, u32 l2, u32 dlid, + u32 sctxt) +{ + u8 pidx = hfi2_loopback ? loopback_pidx(ppd) : ppd->hw_pidx; + + return hfi2_gen_create_pbc_pidx(pidx, flags, srate_mbs, vl, dw_len, l2, dlid, + sctxt); +} + +/* + * Construct a OPA MAD for sending to CPORT. + */ +static struct opa_smp *build_cport_mad(int meth, int attr) +{ + struct opa_smp *mad; + + mad = kzalloc_obj(mad, GFP_KERNEL); + if (!mad) + return mad; + mad->base_version = OPA_MGMT_BASE_VERSION; + mad->mgmt_class = IB_MGMT_CLASS_SUBN_DIRECTED_ROUTE; + mad->class_version = OPA_SM_CLASS_VERSION; + mad->method = meth; + mad->attr_id = attr; + return mad; +} + +/* + * Send a GET PORT_INFO OPA MAD to CPORT to get details on port. + * Caller must kfree() the buffer returned (if not IS_ERR()). + */ +static struct opa_smp *cport_get_portinfo(struct hfi2_devdata *dd, int port) +{ + u8 sb = port; /* 1.. */ + struct opa_smp *mad; + struct opa_smp *rsp; + size_t rsp_len; + int ret; + + mad = build_cport_mad(IB_MGMT_METHOD_GET, IB_SMP_ATTR_PORT_INFO); + if (!mad) + return ERR_PTR(-ENOMEM); + /* + * Set port in attribute modifier field, for PORT_INFO. + */ + mad->attr_mod = cpu_to_be32(0x1000000 | port); +#ifdef DEBUG_CPORT_TRAP + pr_warn("hfi2_%d: %s: send: %02x %02x %02x %02x - %04x %04x %08x\n", + dd->unit, __func__, mad->base_version, mad->mgmt_class, + mad->class_version, mad->method, be16_to_cpu(mad->status), + be16_to_cpu(mad->attr_id), be32_to_cpu(mad->attr_mod)); +#endif + rsp_len = sizeof(*rsp); + rsp = kzalloc(rsp_len, GFP_KERNEL); + if (!rsp) { + kfree(mad); + return ERR_PTR(-ENOMEM); + } + ret = hfi2_cport_send_recv_mad(dd, sb, mad, + sizeof(*mad) - OPA_SMP_DR_DATA_SIZE, rsp, + &rsp_len); + kfree(mad); + if (ret) { + kfree(rsp); + if (ret > 0) + ret = -EINVAL; + return ERR_PTR(ret); + } +#ifdef DEBUG_CPORT_TRAP + pr_warn("hfi2_%d: %s: resp: %02x %02x %02x %02x - %04x %04x %08x\n", + dd->unit, __func__, rsp->base_version, rsp->mgmt_class, + rsp->class_version, rsp->method, be16_to_cpu(rsp->status), + be16_to_cpu(rsp->attr_id), be32_to_cpu(rsp->attr_mod)); +#endif + return rsp; +} + +/* + * Called on PF0 after all new VFs appear. + */ +int hfi2_sriov_sync_ports(struct hfi2_devdata *dd, int si_mask) +{ + struct opa_smp *mad; + struct opa_port_info *pi; + int pidx; + int ret = 0; + + if (dd->is_vf) + return -EINVAL; + for (pidx = 0; pidx < dd->params->num_ports; ++pidx) { + mad = cport_get_portinfo(dd, pidx + 1); + if (IS_ERR(mad)) { + ret = PTR_ERR(mad); + } else { + pi = (struct opa_port_info *)opa_get_smp_data(mad); + ret = hfi2_pf2vf_push_portinfo(&dd->pport[pidx], mad, pi, + si_mask); + kfree(mad); + } + if (!ret) + ret = hfi2_pf2vf_push_sc2vlt(&dd->pport[pidx], si_mask); + } + return ret; +} + +#ifdef DEBUG_CPORT_TRAP +static const char *ps_state_name(struct opa_port_states *ps) +{ + static const char *const state_name[] = { + [IB_PORT_NOP] = "NOP", + [IB_PORT_DOWN] = "DOWN", + [IB_PORT_INIT] = "INIT", + [IB_PORT_ARMED] = "ARMED", + [IB_PORT_ACTIVE] = "ACTIVE", + [IB_PORT_ACTIVE_DEFER] = "ACTIVE_DEFER" + }; + u8 ls = port_states_to_logical_state(ps); + + if (ls > IB_PORT_ACTIVE_DEFER) + return "???"; + return state_name[ls]; +} +#endif + +static void check_cport_state(struct work_struct *work) +{ + struct hfi2_cport *cport = + container_of(work, struct hfi2_cport, psc.work); + struct hfi2_devdata *dd = cport->dd; + struct opa_smp *mad; + struct opa_port_info *pi; + int ret; + int pidx; + + /* + * There should be only one running. Others could abort except for + * the race between checking states and releasing semaphore. + */ + ret = down_killable(&dd->cport->psc.wait); + if (ret) { + atomic_dec(&dd->cport->psc.nq); + return; + } +#ifdef DEBUG_CPORT_TRAP + pr_warn("hfi2_%d: %s: starting port_info loop\n", dd->unit, __func__); +#endif + + for (pidx = 0; pidx < dd->params->num_ports; ++pidx) { + if (!port_available_pidx(dd, pidx)) { + ppd_dev_info( + &dd->pport[pidx], + "Skipping port state check - port not available\n"); + continue; + } + mad = cport_get_portinfo(dd, pidx + 1); + if (IS_ERR(mad)) { + ret = PTR_ERR(mad); + } else { + pi = (struct opa_port_info *)opa_get_smp_data(mad); +#ifdef DEBUG_CPORT_TRAP + pr_warn("hfi2_%d: %s: PORTINFO %d: %s %08x (%x)\n", + dd->unit, __func__, pidx + 1, + ps_state_name(&pi->port_states), + be32_to_cpu(mad->attr_mod), + be16_to_cpu(mad->status)); +#endif + ret = hfi2_update_from_opa_portinfo(&dd->pport[pidx], mad, + pi); + kfree(mad); + } + if (ret) + dd_dev_warn( + dd, + "Failed to update PORT_INFO on port %d (%d)\n", + pidx + 1, ret); + } +#ifdef DEBUG_CPORT_TRAP + pr_warn("hfi2_%d: %s: finished port_info loop\n", dd->unit, __func__); +#endif + atomic_dec(&dd->cport->psc.nq); + up(&dd->cport->psc.wait); +} + +static void handle_cport_trap128(struct hfi2_devdata *dd, + struct cport_trap_status traps) +{ + /* note: traps are already repressed */ +#ifdef DEBUG_CPORT_TRAP + pr_warn("hfi2_%d: %s: TRAP128 psc=%d\n", dd->unit, __func__, + !!(traps.flags & CPORT_TRAP_PSC)); +#endif + + if (atomic_read(&dd->cport->psc.nq) > 1) { +#ifdef DEBUG_CPORT_TRAP + pr_warn("hfi2_%d: %s: TRAP128(s) pending: %d\n", dd->unit, + __func__, atomic_read(&dd->cport->psc.nq)); +#endif + return; + } + atomic_inc(&dd->cport->psc.nq); + queue_work(dd->hfi2_wq, &dd->cport->psc.work); +} + +/* + * This initializes everything necessary to receive and process Port + * State Change TRAPs from CPORT. It also kicks off the initial gathering + * of port states from CPORT. + */ +int hfi2_init_cport_trap128(struct hfi2_devdata *dd) +{ + struct cport_trap_status traps = { 0 }; + int ret = 0; + + if (!dd->cport) + return 0; + + atomic_set(&dd->cport->psc.nq, 0); + sema_init(&dd->cport->psc.wait, 1); + INIT_WORK(&dd->cport->psc.work, check_cport_state); + traps.flags = CPORT_TRAP_PSC; /* Trap 128 Port State Change */ + ret = hfi2_register_cport_trap(dd, traps, handle_cport_trap128); + if (ret) + dd_dev_warn(dd, "Failed to register for CPORT TRAP 128: %d\n", + ret); + else if (!(dd->cport->traps_act.flags & CPORT_TRAP_PSC)) + dd_dev_warn(dd, "CPORT TRAP128 not supported\n"); + /* Fake a TRAP-128 to gather initial port states even if register fails */ + handle_cport_trap128(dd, traps); + return ret; +} + +int hfi2_deinit_cport_trap128(struct hfi2_devdata *dd) +{ + if (!dd->cport || !(dd->cport->traps.flags & CPORT_TRAP_PSC)) + return 0; + return hfi2_deregister_cport_trap(dd, handle_cport_trap128); +} + +static void handle_cport_overtemp(struct hfi2_devdata *dd, + struct cport_trap_status traps) +{ + /* note: traps are already repressed */ + hfi2_overtemp(dd); +} + +/* no deinit_ - clearall_cport_trap() unregisters this */ +int hfi2_init_cport_overtemp(struct hfi2_devdata *dd) +{ + struct cport_trap_status traps = { 0 }; + int ret = 0; + + if (!dd->cport) + return 0; + + traps.flags = CPORT_TRAP_OVTM; /* Over Temp emergency */ + ret = hfi2_register_cport_trap(dd, traps, handle_cport_overtemp); + if (ret) + dd_dev_warn(dd, "Failed to register for CPORT Over Temp: %d\n", + ret); + else if (!(dd->cport->traps_act.flags & CPORT_TRAP_OVTM)) + dd_dev_warn(dd, "CPORT Over-Temp notification not supported\n"); + return ret; +} + +static int cport_goto_offline(struct hfi2_pportdata *ppd, + struct opa_port_info *pi, u8 rem_reason) +{ + u32 previous_state; + + previous_state = ppd->host_link_state; + ppd->host_link_state = HLS_GOING_OFFLINE; + + /* start offline transition */ + if (ppd->offline_disabled_reason == + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_NONE)) + ppd->offline_disabled_reason = + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_TRANSIENT); + + hfi2_update_statusp(ppd, IB_PORT_DOWN); + + /* + * The state in CPORT is now offline. + * - change our state + * - notify others if we were previously in a linkup state + */ + ppd->host_link_state = HLS_DN_OFFLINE; + if (previous_state & HLS_UP) { + /* went down while link was up */ + hfi2_cport_handle_linkup_change(ppd, pi, 0); + } + + /* the active link width (downgrade) is 0 on link down */ + ppd->link_width_active = 0; + ppd->link_width_downgrade_tx_active = 0; + ppd->link_width_downgrade_rx_active = 0; + ppd->current_egress_rate = 0; + return 0; +} + +/* hfi2_set_link_state() for CPORT-based systems. Only update local data. */ +int hfi2_cport_set_link_state(struct hfi2_pportdata *ppd, struct opa_port_info *pi, + u32 state) +{ + struct hfi2_devdata *dd = ppd->dd; + int ret = 0; + int orig_new_state, poll_bounce; + + mutex_lock(&ppd->hls_lock); + + orig_new_state = state; + if (state == HLS_DN_DOWNDEF) + state = HLS_DEFAULT; + + /* interpret poll -> poll as a link bounce */ + poll_bounce = ppd->host_link_state == HLS_DN_POLL && + state == HLS_DN_POLL; + + ppd_dev_info(ppd, "%s: current %s, new %s %s%s\n", __func__, + hfi2_link_state_name(ppd->host_link_state), + hfi2_link_state_name(orig_new_state), + poll_bounce ? "(bounce) " : "", + hfi2_link_state_reason_name(ppd, state)); + + /* + * If we're going to a (HLS_*) link state that implies the logical + * link state is neither of (IB_PORT_ARMED, IB_PORT_ACTIVE), then + * reset is_sm_config_started to 0. + */ + if (!(state & (HLS_UP_ARMED | HLS_UP_ACTIVE))) + ppd->is_sm_config_started = 0; + + /* + * Do nothing if the states match. Let a poll to poll link bounce + * go through. + */ + if (ppd->host_link_state == state && !poll_bounce) + goto done; + + switch (state) { + case HLS_UP_INIT: + hfi2_log_state_transition(ppd, PLS_LINKUP); + + /* clear old transient LINKINIT_REASON code */ + if (ppd->linkinit_reason >= OPA_LINKINIT_REASON_CLEAR) + ppd->linkinit_reason = OPA_LINKINIT_REASON_LINKUP; + + hfi2_cport_handle_linkup_change(ppd, pi, 1); + hfi2_pio_kernel_linkup(ppd); + + /* + * After link up, a new link width will have been set. + * Update the xmit counters with regards to the new + * link width. + */ + hfi2_update_xmit_counters(ppd, ppd->link_width_active); + + ppd->host_link_state = HLS_UP_INIT; + hfi2_update_statusp(ppd, IB_PORT_INIT); + break; + case HLS_UP_ARMED: + if (ppd->host_link_state != HLS_UP_INIT) + dd_dev_err( + dd, + "%s %d: allowing unexpected state transition from %s to %s\n", + __func__, ppd->port, + hfi2_link_state_name(ppd->host_link_state), + hfi2_link_state_name(state)); + + ppd->host_link_state = HLS_UP_ARMED; + hfi2_update_statusp(ppd, IB_PORT_ARMED); + break; + case HLS_UP_ACTIVE: + if (ppd->host_link_state != HLS_UP_ARMED) + dd_dev_err( + dd, + "%s %d: allowing unexpected state transition from %s to %s\n", + __func__, ppd->port, + hfi2_link_state_name(ppd->host_link_state), + hfi2_link_state_name(state)); + + ppd->host_link_state = HLS_UP_ACTIVE; + hfi2_update_statusp(ppd, IB_PORT_ACTIVE); + hfi2_go_port_active(ppd); + break; + case HLS_DN_POLL: + + if (ppd->host_link_state != HLS_DN_OFFLINE) { + u8 tmp = ppd->link_enabled; + + ret = cport_goto_offline(ppd, pi, + ppd->remote_link_down_reason); + if (ret) { + ppd->link_enabled = tmp; + break; + } + ppd->remote_link_down_reason = 0; + + if (ppd->driver_link_ready) + ppd->link_enabled = 1; + } + + hfi2_set_all_slowpath(ppd); + + ppd->port_error_action = 0; + + ppd->host_link_state = HLS_DN_POLL; + ppd->offline_disabled_reason = + HFI2_ODR_MASK(OPA_LINKDOWN_REASON_NONE); + hfi2_log_state_transition(ppd, PLS_POLLING); + break; + case HLS_DN_DISABLE: + /* link is disabled */ + ppd->link_enabled = 0; + + /* allow any state to transition to disabled */ + + /* must transition to offline first */ + if (ppd->host_link_state != HLS_DN_OFFLINE) { + ret = cport_goto_offline(ppd, pi, + ppd->remote_link_down_reason); + if (ret) + break; + ppd->remote_link_down_reason = 0; + } + + ppd->host_link_state = HLS_DN_DISABLE; + break; + case HLS_DN_OFFLINE: + /* allow any state to transition to offline */ + ret = cport_goto_offline(ppd, pi, ppd->remote_link_down_reason); + if (!ret) + ppd->remote_link_down_reason = 0; + break; + case HLS_GOING_UP: /* never seen by driver */ + case HLS_VERIFY_CAP: /* never seen by driver */ + case HLS_GOING_OFFLINE: /* transient within goto_offline() */ + case HLS_LINK_COOLDOWN: /* transient within goto_offline() */ + default: + dd_dev_info(dd, "%s %d: state 0x%x: not supported\n", __func__, + ppd->port, state); + ret = -EINVAL; + break; + } + +done: + mutex_unlock(&ppd->hls_lock); + + return ret; +} + +int hfi2_cport_start_link(struct hfi2_pportdata *ppd, struct opa_port_info *pi) +{ + /* + * FULL_MGMT_P_KEY is cleared from the pkey table, so that the + * pkey table can be configured properly if the HFI unit is connected + * to switch port with MgmtAllowed=NO + */ + /* this writes CSRs... clear_full_mgmt_pkey(ppd); so do: */ + if (ppd->pkeys[2] != 0) { + ppd->pkeys[2] = 0; + /* avoid hfi2_set_ib_cfg(HFI2_IB_CFG_PKEYS) */ + hfi2_event_pkey_change(ppd->dd, ppd->port); + } + + return hfi2_cport_set_link_state(ppd, pi, HLS_DN_POLL); +} + +/** + * hfi2_cport_read_temp - Ask cport firmware for the temperature. + * @dd: device data + * @gen_temp: temperature output. + * + * Return: 0 on success, -EINVAL on invalid reply from CPORT, + * -EOPNOTSUPP on reply from CPORT but ASIC temperature not + * valid/supported. + */ +int hfi2_cport_read_temp(struct hfi2_devdata *dd, struct cport_temp *gen_temp) +{ + struct cport_how_payload *how = NULL; + int resp_len = 0; + int ret; + + /* Don't trust the caller; assume invalid */ + gen_temp->asic_valid = 0; + gen_temp->qsfp1_valid = 0; + gen_temp->qsfp2_valid = 0; + + ret = hfi2_cport_send_req(dd, CH_OP_HOW, 0, NULL, 0, (void **)&how, + &resp_len, hfi2_cport_adm_to * HZ); + if (ret) { + dd_dev_err(dd, "CPORT how failed %d\n", ret); + goto done; + } + if (resp_len != sizeof(*how)) { + dd_dev_err( + dd, + "CPORT how invalid response length %d (expected %ld)\n", + resp_len, sizeof(*how)); + ret = -EINVAL; + goto done; + } + if (!CPORT_HOW_TEMP_VALID(how->temps)) { + ret = -EOPNOTSUPP; + goto done; + } + gen_temp->asic_valid = 1; + gen_temp->asic = (s16)CPORT_HOW_TEMP(how->temps); + + gen_temp->qsfp1_valid = CPORT_HOW_QSFP1_TEMP_VALID(how->temps); + if (gen_temp->qsfp1_valid) + gen_temp->qsfp1 = (s16)CPORT_HOW_QSFP1_TEMP(how->temps); + + gen_temp->qsfp2_valid = CPORT_HOW_QSFP2_TEMP_VALID(how->temps); + if (gen_temp->qsfp2_valid) + gen_temp->qsfp2 = (s16)CPORT_HOW_QSFP2_TEMP(how->temps); +done: + kfree(how); + return ret; +} + +static void gen_reset_rcvarray(struct hfi2_devdata *dd, u16 ctxt, u32 ra_cnt) +{ + u8 __iomem *ra; + u32 off; + u32 idx; + + ra = dd->bar_maps[ctxt_bar_idx(ctxt)].rcvarray_wc; + ctxt = ctxt_bar_ctxt(ctxt); + for (idx = 0; idx < ra_cnt; ++idx) { + off = (ctxt << JKR_RCV_ARRAY_RCV_CTXT_IDX_SHIFT) | + (idx << JKR_RCV_ARRAY_CSR_INDEX_SHIFT); + writeq(RCV_ARRAY_RT_WRITE_ENABLE_SMASK, ra + off); + } + flush_wc(); +} + +/* + * Called on PF0 before VFs are created. + * Context will be used for Eager only (no TID). + * Initialize all CSRs that can only be accessed by PF0. + * May be called to reset context for re-use. + */ +int hfi2_gen_init_rctxt_egr(struct hfi2_devdata *dd, u8 pidx, int si, u16 ctxt, + u32 ra_base, u32 ra_cnt, u32 hdr_size) +{ + u64 reg, kreg; + + /* might need to reclaim context in PF0 */ + if (si) + write_rctxt_csr(dd, ctxt, JKR_RCV_SI_IDX, 0); + + /* reset eager head/tail by enabling ctxt after write of 0 to heads */ + kreg = read_kctxt_csr(dd, ctxt, dd->params->rcv_kctxt_ctrl_reg); + + /* disable context, in case it was previously used */ + hfi2_jkr_ena_rcv_ctxt(dd, pidx, ctxt, false); + /* remove RCV_CTXT_CTRL_ENABLE_SMASK (disable) */ + kreg &= ~RCV_CTXT_CTRL_ENABLE_SMASK; + /* force these bits */ + kreg |= RCV_CTXT_CTRL_ONE_PACKET_PER_EGR_BUFFER_SMASK | + JKR_RCV_KCTXT_CTRL_RECEIVE_CUT_THROUGH_DISABLE_SMASK; + write_kctxt_csr(dd, ctxt, dd->params->rcv_kctxt_ctrl_reg, kreg); + + dd->params->set_port_tid_config(dd, pidx, ctxt, ra_base, ra_cnt, 0, 0); + hfi2_jkr_upd_rcv_hdr_size(dd, pidx, ctxt, hdr_size); + + reg = RCV_CTXT_CTRL_INTR_AVAIL_SMASK; + write_rctxt_csr(dd, ctxt, dd->params->rcv_rctxt_ctrl_reg, reg); + + write_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg, 0); + write_uctxt_csr(dd, ctxt, dd->params->rcv_egr_index_head_reg, 0); + + gen_reset_rcvarray(dd, ctxt, ra_cnt); + + /* (re-)enable context */ + hfi2_jkr_ena_rcv_ctxt(dd, pidx, ctxt, true); + kreg |= RCV_CTXT_CTRL_ENABLE_SMASK; + write_kctxt_csr(dd, ctxt, dd->params->rcv_kctxt_ctrl_reg, kreg); + + /* must be done after enable */ + write_kctxt_csr(dd, ctxt, dd->params->rcv_avail_time_out_reg, + RCV_AVAIL_TIME_OUT_TIME_OUT_RELOAD_MASK + << RCV_AVAIL_TIME_OUT_TIME_OUT_RELOAD_SHIFT); + hfi2_update_usrhead_ctxt(dd, ctxt, 0, 1, 0, 0); /* needed for interrupts */ + + /* + * Leave something for the VF to probe on. + * Set any non-zero value, will be changed by VF later. + */ + reg = ((u64)hfi2_encode_rcv_header_entry_size(32) & + RCV_HDR_ENT_SIZE_ENT_SIZE_MASK) + << RCV_HDR_ENT_SIZE_ENT_SIZE_SHIFT; + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_ent_size_reg, reg); + + /* finally, assign context to VF */ + if (si) + write_rctxt_csr(dd, ctxt, JKR_RCV_SI_IDX, si); + return 0; +} + +void hfi2_gen_deinit_rctxt(struct hfi2_devdata *dd, u8 pidx, int si, u16 ctxt) +{ + u32 ra_cnt; + + /* first, assign context back to PF0 */ + if (si) + write_rctxt_csr(dd, ctxt, JKR_RCV_SI_IDX, 0); + + ra_cnt = ((read_rctxt_csr(dd, ctxt, dd->params->rcv_egr_ctrl_reg) >> + RCV_EGR_CTRL_EGR_CNT_SHIFT) & + RCV_EGR_CTRL_EGR_CNT_MASK) + << RCV_SHIFT; + hfi2_jkr_ena_rcv_ctxt(dd, pidx, ctxt, false); + + gen_reset_rcvarray(dd, ctxt, ra_cnt); + + write_kctxt_csr(dd, ctxt, dd->params->rcv_kctxt_ctrl_reg, 0); + write_rctxt_csr(dd, ctxt, dd->params->rcv_rctxt_ctrl_reg, 0); + write_rctxt_csr(dd, ctxt, dd->params->rcv_egr_ctrl_reg, 0); + + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_cnt_reg, 0); + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_ent_size_reg, 0); + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_addr_reg, 0); + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_tail_addr_reg, 0); +} + +/* + * Called by VFs before first VF-PF message. + */ +int hfi2_gen_start_rctxt_egr(struct hfi2_devdata *dd, u8 pidx, u16 ctxt, + struct hfi2_ctxtbufs *bufs) +{ + u8 __iomem *ra; + u16 order; + u32 off; + u64 reg; + u32 r_each, r_size, etail; + dma_addr_t r_dma; + int idx; + + /* cleanup from anything sent while no driver */ + etail = read_uctxt_csr(dd, ctxt, JKR_RCV_EGR_INDEX_TAIL) & 0xffff; + if (etail) + hfi2_update_usrhead_ctxt(dd, ctxt, 0, 1, 1, etail); + /* just clear overflow coount - can't do anything else */ + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_ovfl_cnt_reg, 0); + + /* assumes RCV_CTXT_CTRL_ONE_PACKET_PER_EGR_BUFFER_SMASK is set */ + r_each = bufs->egr_buf_size; + r_dma = bufs->egr.dma; + r_size = bufs->egr.size; + idx = 0; + order = hfi2_encoded_size(r_each); + ra = dd->bar_maps[ctxt_bar_idx(ctxt)].rcvarray_wc; + while (r_size >= r_each) { + off = (ctxt_bar_ctxt(ctxt) + << JKR_RCV_ARRAY_RCV_CTXT_IDX_SHIFT) | + (idx << JKR_RCV_ARRAY_CSR_INDEX_SHIFT); + reg = RCV_ARRAY_RT_WRITE_ENABLE_SMASK | + ((u64)order << JKR_RCV_ARRAY_EGR_RT_BUF_SIZE_SHIFT) | + (r_dma >> RT_ADDR_SHIFT); + writeq(reg, ra + off); + ++idx; + r_size -= r_each; + r_dma += r_each; + } + flush_wc(); + if (!idx) /* none allocated */ + return -ENOSPC; + + reg = (((u64)bufs->rhq_cnt >> HDRQ_SIZE_SHIFT) & RCV_HDR_CNT_CNT_MASK) + << RCV_HDR_CNT_CNT_SHIFT; + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_cnt_reg, reg); + reg = ((u64)hfi2_encode_rcv_header_entry_size(bufs->rhq_ent_size) & + RCV_HDR_ENT_SIZE_ENT_SIZE_MASK) + << RCV_HDR_ENT_SIZE_ENT_SIZE_SHIFT; + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_ent_size_reg, reg); + + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_addr_reg, bufs->rhq.dma); + if (dd->params->set_rheq_addr) + dd->params->set_rheq_addr(dd, ctxt, bufs->rheq.dma); + + write_kctxt_csr(dd, ctxt, dd->params->rcv_hdr_tail_addr_reg, + dd->rcvhdrtail_dummy_dma); + + return 0; +} + +/* + * Called on PF0 before VFs are created. + * Context is used for PIO only (no SDMA). + * Initialize all CSRs that can only be accessed by PF0. + */ +int hfi2_gen_init_sctxt_pio(struct hfi2_devdata *dd, u8 pidx, int si, u16 ctxt, + u32 cr_base, u32 cr_cnt) +{ + u64 reg; + int ret; + + /* might need to reclaim context in PF0 */ + if (si) + hfi2_write_csr(dd, JKR_SEND_CTXT_SI_IDX + (8 * ctxt), 0); + + /* first, ensure context is disabled - to ensure reset */ + ret = hfi2_priv_reg_op(dd, pidx, ctxt, SC_KERNEL, SC_DISABLE_OP, 0); + if (ret) + return ret; + + reg = ((u64)cr_cnt << SEND_CTXT_CTRL_CTXT_DEPTH_SHIFT) | + ((u64)cr_base << dd->params->pio_base_shift); + write_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg, reg); + /* or: dd->params->set_pio_integrity(dd, pidx, ctxt, SC_KERNEL, SPI_INIT) */ + write_epsc_csr(dd, pidx, ctxt, dd->params->send_ctxt_check_enable_reg, + JKR_SEND_CTXT_CHECK_ENABLE_L2_TYPE9BALLOWED_SMASK); + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_partition_key_reg, + (SEND_CTXT_CHECK_PARTITION_KEY_VALUE_MASK & DEFAULT_PKEY) + << SEND_CTXT_CHECK_PARTITION_KEY_VALUE_SHIFT); + write_epsc_csr(dd, pidx, ctxt, dd->params->send_ctxt_check_opcode_reg, + ((u64)OPCODE_CHECK_MASK_DISABLED + << SEND_CTXT_CHECK_OPCODE_MASK_SHIFT) | + ((u64)OPCODE_CHECK_VAL_DISABLED + << SEND_CTXT_CHECK_OPCODE_VALUE_SHIFT)); + + write_sctxt_csr(dd, ctxt, dd->params->send_ctxt_err_mask_reg, 0); + write_sctxt_csr(dd, ctxt, dd->params->send_ctxt_credit_return_addr_reg, + 0); + + reg = 1 << SEND_CTXT_CREDIT_CTRL_THRESHOLD_SHIFT; + write_sctxt_csr(dd, ctxt, dd->params->send_ctxt_credit_ctrl_reg, reg); + /* send_ctxt_check_vl_reg stays 0? */ + + /* this does a PIO init on the context */ + ret = hfi2_priv_reg_op(dd, pidx, ctxt, SC_KERNEL, SC_ENABLE_OP, 0); + if (ret) + return ret; + + /* finally, assign context to VF */ + if (si) + hfi2_write_csr(dd, JKR_SEND_CTXT_SI_IDX + (8 * ctxt), si); + + return 0; +} + +void hfi2_gen_deinit_sctxt(struct hfi2_devdata *dd, u8 pidx, int si, u16 ctxt) +{ + /* first, assign context back to PF0 */ + if (si) + hfi2_write_csr(dd, JKR_SEND_CTXT_SI_IDX + (8 * ctxt), 0); + + write_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg, 0); + write_epsc_csr(dd, pidx, ctxt, dd->params->send_ctxt_check_enable_reg, + 0); + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_partition_key_reg, 0); + write_epsc_csr(dd, pidx, ctxt, dd->params->send_ctxt_check_opcode_reg, + 0); + write_sctxt_csr(dd, ctxt, dd->params->send_ctxt_err_mask_reg, 0); + write_sctxt_csr(dd, ctxt, dd->params->send_ctxt_credit_return_addr_reg, + 0); + write_sctxt_csr(dd, ctxt, dd->params->send_ctxt_credit_ctrl_reg, 0); +} + +/* + * Called by VFs before first VF-PF message. + */ +int hfi2_gen_start_sctxt(struct hfi2_devdata *dd, u8 pidx, u16 ctxt, + struct hfi2_ctxtbufs *bufs) +{ + write_sctxt_csr(dd, ctxt, dd->params->send_ctxt_err_mask_reg, (u64)-1); + write_sctxt_csr(dd, ctxt, dd->params->send_ctxt_credit_return_addr_reg, + bufs->cr.dma); + return 0; +} + +static void set_sc_check(struct hfi2_devdata *dd, u8 pidx, u32 ctxt, int type) +{ + u8 opval, opmask; + + /* set the default partition key */ + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_partition_key_reg, + (SC(CHECK_PARTITION_KEY_VALUE_MASK) & DEFAULT_PKEY) + << SC(CHECK_PARTITION_KEY_VALUE_SHIFT)); + /* per context type checks */ + if (type == SC_USER) { + opval = USER_OPCODE_CHECK_VAL; + opmask = USER_OPCODE_CHECK_MASK; + } else { + opval = OPCODE_CHECK_VAL_DISABLED; + opmask = OPCODE_CHECK_MASK_DISABLED; + } + /* set the send context check opcode mask and value */ + write_epsc_csr(dd, pidx, ctxt, dd->params->send_ctxt_check_opcode_reg, + ((u64)opmask << SC(CHECK_OPCODE_MASK_SHIFT)) | + ((u64)opval << SC(CHECK_OPCODE_VALUE_SHIFT))); + /* User send contexts should not allow sending on VL15 */ + if (type == SC_USER) { + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_vl_reg, 1ULL << 15); + } +} + +/* + * Read a CSR based on type + * + * type - CSR_TYPE_* + * off - base offset of CSR + * ctxt - conext number, if type requires one + * pidx_eng - port index or SDMA engine number, depending on type + */ +u64 hfi2_read_csr_type(struct hfi2_devdata *dd, enum csr_type type, u32 off, + u16 ctxt, u8 pidx_eng) +{ + u64 reg = ~0ull; + + switch (type) { + case CSR_TYPE_IPORT: + reg = read_iport_csr(dd, pidx_eng, off); + break; + case CSR_TYPE_IPRC: + reg = read_iprc_csr(dd, pidx_eng, ctxt, off); + break; + case CSR_TYPE_RCTXT: + reg = read_rctxt_csr(dd, ctxt, off); + break; + case CSR_TYPE_KCTXT: + reg = read_kctxt_csr(dd, ctxt, off); + break; + case CSR_TYPE_KU: + reg = read_ku_csr(dd, ctxt, off); + break; + case CSR_TYPE_UCTXT: + reg = read_uctxt_csr(dd, ctxt, off); + break; + case CSR_TYPE_SCTXT: + reg = read_sctxt_csr(dd, ctxt, off); + break; + case CSR_TYPE_TCTXT: + reg = read_tctxt_csr(dd, ctxt, off); + break; + case CSR_TYPE_SDMA: + reg = read_sdma_csr(dd, pidx_eng, off); + break; + case CSR_TYPE_SDMACFG: + reg = read_sdmacfg_csr(dd, pidx_eng, off); + break; + case CSR_TYPE_EPORT: + reg = read_eport_csr(dd, pidx_eng, off); + break; + case CSR_TYPE_EPSC: + reg = read_epsc_csr(dd, pidx_eng, ctxt, off); + break; + case CSR_TYPE_EPSCARR: + reg = read_epsc_csr(dd, pidx_eng, ctxt, off); + break; + } + return reg; +} + +int hfi2_priv_reg_op(struct hfi2_devdata *dd, int pidx, u32 ctxt, int type, + enum preg_op op, u64 arg) +{ + u16 rctxt; + int ret = 0; + + rctxt = ctxt >> 16; + ctxt &= 0xffff; + + if (dd->is_vf) { + ret = hfi2_vf2pf_priv_reg_op(dd, pidx, ctxt, type, op, arg); + if (ret) + dd_dev_err(dd, "hfi2_vf2pf_priv_reg_op(%d) failed %d\n", op, + ret); + return ret; + } + + /* Only PF0 has access to these CSRs */ + switch (op) { + case SC_CHK_ALLOC_OP: /* 'arg' is send_ctxt_ctrl_reg value */ + write_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg, arg); + dd->params->set_pio_integrity(dd, pidx, ctxt, type, + SPI_DEFAULT); + set_sc_check(dd, pidx, ctxt, type); + if (dd->is_sriov) + set_sc_check(dd, loopback_pidx_dd(dd, pidx), ctxt, + type); + break; + case SC_CHK_FREE_OP: /* 'arg' not used */ + write_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg, 0); + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_enable_reg, 0); + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_partition_key_reg, + 0); + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_opcode_reg, 0); + if (dd->is_sriov) { + pidx = loopback_pidx_dd(dd, pidx); + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_enable_reg, + 0); + write_epsc_csr( + dd, pidx, ctxt, + dd->params->send_ctxt_check_partition_key_reg, + 0); + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_opcode_reg, + 0); + } + break; + case SC_CHK_VL_MASK_OP: /* 'arg' is send_ctxt_check_vl_reg value */ + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_vl_reg, arg); + if (dd->is_sriov) + write_epsc_csr(dd, loopback_pidx_dd(dd, pidx), ctxt, + dd->params->send_ctxt_check_vl_reg, arg); + break; + case SC_CHK_SLID_OP: /* 'arg' is send_ctxt_check_slid_reg value */ + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_slid_reg, arg); + if (dd->is_sriov) + write_epsc_csr(dd, loopback_pidx_dd(dd, pidx), ctxt, + dd->params->send_ctxt_check_slid_reg, + arg); + break; + case SC_CHK_JKEY_OP: /* 'arg' is send_ctxt_check_job_key_reg val, 'ctxt' incl rcv */ + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_job_key_reg, arg); + if (dd->is_sriov) + write_epsc_csr(dd, loopback_pidx_dd(dd, pidx), ctxt, + dd->params->send_ctxt_check_job_key_reg, + arg); + if (!hfi2_is_ax(dd)) { + dd->params->set_pio_integrity(dd, pidx, ctxt, type, + arg ? SPI_SET_JKEY : + SPI_CLEAR_JKEY); + } + /* Enable/clear J_KEY check on receive context. */ + if (arg) { + /* convert sctxt jkey to rctxt */ + arg = (arg >> SEND_CTXT_CHECK_JOB_KEY_VALUE_SHIFT) & + SEND_CTXT_CHECK_JOB_KEY_VALUE_MASK; + arg = RCV_KEY_CTRL_JOB_KEY_ENABLE_SMASK | + ((arg & RCV_KEY_CTRL_JOB_KEY_VALUE_MASK) + << RCV_KEY_CTRL_JOB_KEY_VALUE_SHIFT); + } + write_iprc_csr(dd, pidx, rctxt, dd->params->rcv_jkey_ctrl_reg, + arg); + if (dd->is_sriov) + write_iprc_csr(dd, loopback_pidx_dd(dd, pidx), rctxt, + dd->params->rcv_jkey_ctrl_reg, arg); + break; + case SC_CHK_PKEY_OP: /* 'arg' is send_ctxt_check_partition_key_reg value */ + if (!arg) + dd->params->set_pio_integrity(dd, pidx, ctxt, type, + SPI_CLEAR_PKEY); + write_epsc_csr(dd, pidx, ctxt, + dd->params->send_ctxt_check_partition_key_reg, + arg); + if (dd->is_sriov) + write_epsc_csr( + dd, loopback_pidx_dd(dd, pidx), ctxt, + dd->params->send_ctxt_check_partition_key_reg, + arg); + if (arg) + dd->params->set_pio_integrity(dd, pidx, ctxt, type, + SPI_SET_PKEY); + break; + case SC_CHK_ADJ_OP: /* 'arg' is enable flag (do SC_CHK_INIT_OP also) */ + dd->params->set_pio_integrity(dd, pidx, ctxt, type, + SPI_DEFAULT); + if (!arg) + break; + fallthrough; + case SC_CHK_INIT_OP: /* 'arg' not used */ + dd->params->set_pio_integrity(dd, pidx, ctxt, type, SPI_INIT); + break; + case SC_ENABLE_OP: /* 'arg' not used as input, 'pidx' not used */ + ret = hfi2_pio_reset_one(dd, ctxt); + if (ret) + break; + + /* + * All is well. Enable the context. + */ + arg = read_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg); + arg |= SC(CTRL_CTXT_ENABLE_SMASK); + write_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg, arg); + /* + * Read SendCtxtCtrl to force the write out and prevent a timing + * hazard where a PIO write may reach the context before the enable. + */ + read_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg); + break; + case SC_DISABLE_OP: /* 'arg' not used as input, 'pidx' not used */ + arg = read_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg); + arg &= ~SC(CTRL_CTXT_ENABLE_SMASK); + write_tctxt_csr(dd, ctxt, dd->params->send_ctxt_ctrl_reg, arg); + break; + case RC_ENABLE_OP: /* 'arg' is enable flag */ + hfi2_jkr_ena_rcv_ctxt(dd, pidx, ctxt, arg); + if (dd->is_sriov) + hfi2_jkr_ena_rcv_ctxt(dd, loopback_pidx_dd(dd, pidx), ctxt, + arg); + break; + case RC_HEADER_OP: /* 'arg' is size */ + hfi2_jkr_upd_rcv_hdr_size(dd, pidx, ctxt, arg); + if (dd->is_sriov) + hfi2_jkr_upd_rcv_hdr_size(dd, loopback_pidx_dd(dd, pidx), + ctxt, arg); + break; + case LINK_BOUNCE_OP: /* 'arg' is not used */ + queue_work(dd->pport[pidx].link_wq, + &dd->pport[pidx].link_bounce_work); + break; + } + return ret; +} diff --git a/drivers/infiniband/hw/hfi2/chip_jkr.c b/drivers/infiniband/hw/hfi2/chip_jkr.c new file mode 100644 index 000000000000..92c7594e0739 --- /dev/null +++ b/drivers/infiniband/hw/hfi2/chip_jkr.c @@ -0,0 +1,1017 @@ +// SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause +/* + * Copyright(c) 2025-2026 Cornelis Networks, Inc. + */ + +#include "hfi2.h" +#include "trace.h" +#include "chip_jkr.h" +#include "cport.h" +#include "sriov.h" +#include "vf2pf.h" + +int hfi2_jkr_find_used_resources(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs *dr = &dd->rsrcs; + u64 val; + bool found_first_unused; + int i; + + /* + * This sets up boundaries only. PF0 must initialize all and + * assign to SI(s), as well as avoid CPORT ones. + */ + if (dr->num_vfs) { + /* resources already setup by hfi2_sriov_set_cfg() */ + if (dd->is_vf) + goto out; + } else { + dr->pfunit = dd->unit; + dr->c.first_send_context = 0; + dr->c.last_send_context = chip_send_contexts(dd); + dr->c.first_rcv_context = 0; + dr->c.last_rcv_context = chip_rcv_contexts(dd); + dr->c.first_rcvarray_entry = 0; + dr->c.last_rcvarray_entry = chip_rcv_array_count(dd); + dr->c.first_pio_block = 0; + dr->c.last_pio_block = chip_pio_mem_size(dd) / PIO_BLOCK_SIZE; + } + + /* + * Find reserved resources. Expectations: All used resources are + * at the front of the resource. If this is not the case, there + * will be wasted resources. + */ + + /* + * Look for send reserved. + */ + found_first_unused = false; + for (i = 0; i < dr->c.last_send_context; i++) { + val = hfi2_read_ctxt_csr(dd, JKR_SEND_CTXT_SI_IDX, i, 8); + if (val == 0) { /* 0 means pf0 */ + /* this context is for the driver */ + if (!found_first_unused) { + found_first_unused = true; + dr->c.first_send_context = i; + } + } else { + u32 base; /* in blocks */ + u32 size; /* in blocks */ + + /* this context is non-driver */ + if (found_first_unused) { + /* + * Expect an initial set of non-driver + * contexts, then all driver after that. + */ + return -EINVAL; + } + /* read PIO send resources for this context */ + val = read_tctxt_csr(dd, i, + dd->params->send_ctxt_ctrl_reg); + base = (val >> JKR_SEND_CTXT_CTRL_CTXT_BASE_SHIFT) & + MASK_ULL(dd->params->pio_base_bits); + size = (val >> SEND_CTXT_CTRL_CTXT_DEPTH_SHIFT) & + SEND_CTXT_CTRL_CTXT_DEPTH_MASK; + dd_dev_info( + dd, + "Non driver send ctxt %d: base 0x%x, size 0x%x\n", + i, base, size); + /* + * Expect the non-driver contexts to use the blocks in + * increasing groups. Warn otherwise. This is a simple + * attempt to warn if there may be wasted reserved + * blocks. I.e. no holes. Doing this right would + * involve much more complicated range lists that are + * not worth doing. + */ + if (dr->c.first_pio_block != base) { + dd_dev_warn( + dd, + "%s: WARNING: unexpected PIO blocks used\n", + __func__); + } + /* adjust top used */ + if (dr->c.first_pio_block < base + size) + dr->c.first_pio_block = base + size; + } + } + if (dr->c.first_send_context >= dr->c.last_send_context) + return -ENOSPC; + + /* + * Look for receive reserved. + */ + found_first_unused = false; + for (i = 0; i < dr->c.last_rcv_context; i++) { + val = read_rctxt_csr(dd, i, JKR_RCV_SI_IDX); + if (val == 0) { /* 0 means pf0 */ + /* this context is for the driver */ + if (!found_first_unused) { + found_first_unused = true; + dr->c.first_rcv_context = i; + } + } else { + u32 egr_base; + u32 egr_count; + u32 tid_base; + u32 tid_count; + + /* this context is non-driver */ + if (found_first_unused) { + /* + * Expect an initial set of non-driver + * contexts, then all driver after that. + */ + return -EINVAL; + } + + /* read resources for this context */ + /* RcvEgrCtrl RcvTidCtrl */ + val = read_rctxt_csr(dd, i, + dd->params->rcv_egr_ctrl_reg); + egr_base = + (val >> JKR_RCV_EGR_CTRL_EGR_BASE_INDEX_SHIFT) & + JKR_RCV_EGR_CTRL_EGR_BASE_INDEX_MASK; + egr_count = (val >> JKR_RCV_EGR_CTRL_EGR_CNT_SHIFT) & + JKR_RCV_EGR_CTRL_EGR_CNT_MASK; + val = read_rctxt_csr(dd, i, + dd->params->rcv_tid_ctrl_reg); + tid_base = + (val >> JKR_RCV_TID_CTRL_TID_BASE_INDEX_SHIFT) & + JKR_RCV_TID_CTRL_TID_BASE_INDEX_MASK; + tid_count = + (val >> JKR_RCV_TID_CTRL_TID_PAIR_CNT_SHIFT) & + JKR_RCV_TID_CTRL_TID_PAIR_CNT_MASK; + dd_dev_info( + dd, + "Non driver rcv ctxt %d: egr_base 0x%x, egr_count 0x%x, tid_base 0x%x, tid_count 0x%x\n", + i, egr_base, egr_count, tid_base, tid_count); + /* expect no TID resources used */ + if (tid_count != 0) + return -EINVAL; + + /* convert from group to individual counts */ + egr_base *= RCV_INCREMENT; + egr_count *= RCV_INCREMENT; + + /* + * Expect the non-driver contexts to use the entries in + * increasing groups. Warn otherwise. This is a simple + * attempt to warn if there may be wasted reserved + * blocks. I.e. no holes. Doing this right would + * involve much more complicated range lists that are + * not worth doing. + */ + if (dr->c.first_rcvarray_entry != egr_base) { + dd_dev_warn( + dd, + "%s: WARNING: unexpected RcvArray entries used\n", + __func__); + } + + if (dr->c.first_rcvarray_entry < egr_base + egr_count) + dr->c.first_rcvarray_entry = + egr_base + egr_count; + } + } + if (dr->c.first_rcv_context >= dr->c.last_rcv_context) + return -ENOSPC; + + /* + * Look for RSM rules being used. + */ + for (i = 0; i < dd->params->rsm_rule_size; i++) { + val = hfi2_read_csr(dd, JKR_RCV_RSM_CFG + (8 * i)); + if (val == 0) + break; + } + if (i == dd->params->rsm_rule_size) { + dd_dev_err(dd, "All %d RSM rules used\n", + dd->params->rsm_rule_size); + return -EINVAL; + } + dd->first_rsm_rule = i; + /* mark these as used */ + for (i = 0; i < dd->first_rsm_rule; i++) + set_bit(i, dd->rsm_rule_bitmap); + dd->rsm_rule_init = true; + +out: + dd_dev_info( + dd, + "Resource starts: send ctxt %d, pio block %d, rcv ctxt %d, RcvArray %d, rsm rule %d\n", + dr->c.first_send_context, dr->c.first_pio_block, + dr->c.first_rcv_context, dr->c.first_rcvarray_entry, + dd->first_rsm_rule); + + return 0; +} + +void hfi2_jkr_read_guid(struct hfi2_devdata *dd) +{ + /* This should get refactored into early_per_chip_init() for all */ +} + +int hfi2_jkr_early_per_chip_init(struct hfi2_devdata *dd) +{ + hfi2_tune_pcie_caps(dd); + hfi2_init_early_variables(dd); + + return hfi2_sriov_assign_rsrcs(dd, &dd->rsrcs); +} + +int hfi2_jkr_mid_per_chip_init(struct hfi2_devdata *dd) +{ + struct cport_who_payload *who = NULL; + int resp_len = 0; + int ret = 0; + + if (dd->is_vf) + goto skip_guid; /* guid obtained earlier via hfi2_sriov_set_cfg */ + + dd->base_guid = 0xabcd; /* on success, a valid value is set */ + ret = hfi2_cport_send_req(dd, CH_OP_WHO, 0, NULL, 0, (void **)&who, + &resp_len, hfi2_cport_adm_to * HZ); + if (ret) { + dd_dev_err(dd, "CPORT who failed %d\n", ret); + } else if (resp_len == sizeof(*who)) { + struct ib_device *ibdev = &dd->verbs_dev.rdi.ibdev; + char v_str[IB_FW_VERSION_NAME_MAX] = {}; + + dd->base_guid = who->node_guid; + if (!hfi2_ib_hfi2_sys_image_guid) + hfi2_ib_hfi2_sys_image_guid = + cpu_to_be64(dd->base_guid); + dd->cport_ver = who->vers; + hfi2_cport_get_dev_fw_str(ibdev, v_str); + dd_dev_info(dd, "CPORT firmware version %s\n", v_str); + } else + dd_dev_err(dd, "CPORT who invalid resp %d\n", resp_len); + + kfree(who); +skip_guid: + /* additional mid-init here */ + return ret; +} + +static void set_si_int_enable_range(struct hfi2_devdata *dd, u64 *csrs, + u32 start, u32 end) +{ + int i; + u32 idx; + u32 bit; + + for (i = start; i < end; ++i) { + idx = i / 64; + bit = i % 64; + csrs[idx] |= (1ull << bit); + } +} + +static void write_si_int_enable(struct hfi2_devdata *dd, int si, u64 *csrs) +{ + int i; + u32 base; + + base = JKR_CCE_SI_INT_ENABLES + JKR_C_CCE_SI_INT_ENABLES_STRIDE * si; + for (i = 0; i < dd->params->num_int_csrs; ++i) + hfi2_write_csr(dd, base + (i * 8), csrs[i]); +} + +/* non-RXE, non-TXE, csr init */ +void hfi2_jkr_init_other(struct hfi2_devdata *dd) +{ + struct hfi2_devrsrcs dr; + int si, nsi; + u64 csrs[LARGEST_NUM_INT_CSRS]; + u32 is_base; + + if (dd->is_vf) + return; /* VFs can't access these CSRs */ + + nsi = dd->rsrcs.num_vfs + 1; /* #VFs + PF0 */ + + /* Enable interrupts for each SI according to allocated resources */ + for (si = 0; si < nsi; ++si) { + if (si) + hfi2_sriov_get_config(dd, &dr, si); + else + dr = dd->rsrcs; + memset(csrs, 0, sizeof(csrs)); + if (!si) { + set_si_int_enable_range(dd, csrs, + JKR_IS_GENERAL_ERR_START, + JKR_ASIC_ERR_INT + 1); + set_si_int_enable_range( + dd, csrs, JKR_MCTXT_CPORT_TO_PCIE_INT, + JKR_MCTXT_CPORT_TO_PCIE_INT + 1); + } + is_base = dd->params->is_sdmaeng_err_start; + set_si_int_enable_range(dd, csrs, + is_base + dr.first_sdma_engine, + is_base + dr.last_sdma_engine); + is_base = JKR_IS_SENDCTXT_ERR_START; + set_si_int_enable_range(dd, csrs, + is_base + dr.c.first_send_context, + is_base + dr.c.last_send_context); + is_base = dd->params->is_sdma_start; + set_si_int_enable_range(dd, csrs, + is_base + dr.first_sdma_engine, + is_base + dr.last_sdma_engine); + is_base = dd->params->is_sdma_progress_start; + set_si_int_enable_range(dd, csrs, + is_base + dr.first_sdma_engine, + is_base + dr.last_sdma_engine); + is_base = dd->params->is_sdma_idle_start; + set_si_int_enable_range(dd, csrs, + is_base + dr.first_sdma_engine, + is_base + dr.last_sdma_engine); + is_base = dd->params->is_rcvavail_start; + set_si_int_enable_range(dd, csrs, + is_base + dr.c.first_rcv_context, + is_base + dr.c.last_rcv_context); + is_base = dd->params->is_rcvurgent_start; + set_si_int_enable_range(dd, csrs, + is_base + dr.c.first_rcv_context, + is_base + dr.c.last_rcv_context); + is_base = JKR_IS_SENDCREDIT_START; + set_si_int_enable_range(dd, csrs, + is_base + dr.c.first_send_context, + is_base + dr.c.last_send_context); + is_base = JKR_IS_PBC_START; + set_si_int_enable_range(dd, csrs, + is_base + dr.c.first_send_context, + is_base + dr.c.last_send_context); + is_base = JKR_IS_PIO_ERR_START; + set_si_int_enable_range(dd, csrs, is_base + si, + is_base + si + 1); + is_base = JKR_IS_SDMA_ERR_SI_START; + set_si_int_enable_range(dd, csrs, is_base + si, + is_base + si + 1); + is_base = JKR_IS_CSR_ERR_START; + set_si_int_enable_range(dd, csrs, is_base + si, + is_base + si + 1); + hfi2_vf2pf_set_si_enables(dd, si, csrs, + set_si_int_enable_range); + write_si_int_enable(dd, si, csrs); + } +} + +/* all "misc" interrupt source names */ +static const char *const jkr_misc_names[] = { + "CceErrInt", /* 0 */ + "CceSpcFreezeInt", /* 1 */ + "AsicErrInt", /* 2 */ + "cfg_vpd_int", /* 3 */ + "MctxtCportToPcieInt", /* 4 */ + "MctxtPcieToCportInt", /* 5 */ + "CportVdmRxInt", /* 6 */ + "CportVdmTxInt", /* 7 */ + "FlrInt0", /* 8 */ + "FlrInt1", /* 9 */ + "FlrInt2", /* 10 */ + "FlrInt3", /* 11 */ + "FlrInt4", /* 12 */ + "FlrInt5", /* 13 */ + "FlrInt6", /* 14 */ + "FlrInt7", /* 15 */ +}; + +/* all "various" interrupt source names */ +static const char *const jkr_various_names[] = { + "GpioAssertInt", + "PcoreResetInt", + "app_ltssm_enable_int", + "TCritInt", +}; + +/* generic routine for returning names from a table */ +static void gen_name(char *buf, size_t bsize, unsigned int source, + const char *const *names, size_t nsize, const char *detail) +{ + if (source < nsize) + strscpy(buf, names[source], bsize); + else + snprintf(buf, bsize, "%s%u (invalid)", detail, source); +} + +static char *jkr_is_misc_name(char *buf, size_t bsize, unsigned int source) +{ + gen_name(buf, bsize, source, jkr_misc_names, ARRAY_SIZE(jkr_misc_names), + "MiscInt"); + return buf; +} + +static char *jkr_is_various_name(char *buf, size_t bsize, unsigned int source) +{ + gen_name(buf, bsize, source, jkr_various_names, + ARRAY_SIZE(jkr_various_names), "VariousInt"); + return buf; +} + +static char *jkr_is_port_name(char *buf, size_t bsize, unsigned int source) +{ + /* ports have 8 interrupts each */ + snprintf(buf, bsize, "Port%uInt%u", source / 8, source % 8); + return buf; +} + +static char *jkr_is_pcb_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "PbcInt%u", source); + return buf; +} + +static char *jkr_is_pio_err_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "PioErrInt%u", source); + return buf; +} + +static char *jkr_is_sdma_err_si_name(char *buf, size_t bsize, + unsigned int source) +{ + snprintf(buf, bsize, "SdmaErrSiInt%u", source); + return buf; +} + +static char *jkr_is_csr_err_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "CsrErrInt%u", source); + return buf; +} + +static char *jkr_is_reserved_name(char *buf, size_t bsize, unsigned int source) +{ + snprintf(buf, bsize, "Reserved%u", source + JKR_IS_RESERVED_START); + return buf; +} + +static void jkr_handle_cce_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + dd_dev_warn(dd, "%s: unhandled 0x%016llx\n", __func__, reg); +} + +static void jkr_handle_spc_freeze(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + dd_dev_warn(dd, "%s: unhandled 0x%016llx\n", __func__, reg); +} + +static void jkr_handle_asic_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + dd_dev_warn(dd, "%s: unhandled 0x%016llx\n", __func__, reg); +} + +static void jkr_handle_csr_err(struct hfi2_devdata *dd, u32 unused, u64 reg) +{ + dd_dev_warn(dd, "%s: unhandled 0x%016llx\n", __func__, reg); +} + +/* misc errs that need a clear down are also the first 3 */ +static const struct err_reg_info jkr_misc_errs[] = { + EE_N(JKR_CCE_ERR, jkr_handle_cce_err, "CceErr"), + EE_N(JKR_CCE_SPC_FREEZE_INT, jkr_handle_spc_freeze, "CceSpcFreeze"), + EE_N(JKR_ASIC_ERR, jkr_handle_asic_err, "AsicErr"), +}; + +static const struct err_reg_info jkr_sdma_eng_err = + EE_S(JKR_SEND_DMA_ENG_ERR, hfi2_handle_sdma_eng_err, "SDmaEngErr"); + +static const struct err_reg_info jkr_send_pio_err = + EE_N(JKR_SEND_PIO_ERR, hfi2_handle_pio_err, "SendPioErr"); + +static const struct err_reg_info jkr_send_dma_err = + EE_N(JKR_SEND_DMA_ERR, hfi2_handle_sdma_err, "SendDmaErr"); + +static const struct err_reg_info jkr_csr_err = + EE_N(JKR_CSR_ERR, jkr_handle_csr_err, "CsrErr"); + +static const struct err_reg_info jkr_send_egress_err = + EE_E(JKR_SEND_EGRESS_ERR, hfi2_handle_egress_err, "SendEgressErr"); + +static const struct err_reg_info jkr_rcv_err = + EE_I(JKR_RCV_ERR, hfi2_handle_rxe_err, "RcvErr"); + +static void jkr_is_misc_int(struct hfi2_devdata *dd, unsigned int source) +{ + char name[64]; + + /* jkr_misc_errs[] has all interrupts that need a clear down */ + if (source < ARRAY_SIZE(jkr_misc_errs)) { + hfi2_interrupt_clear_down(dd, 0, &jkr_misc_errs[source]); + return; + } + + if (source == JKR_MCTXT_CPORT_TO_PCIE_INT - JKR_IS_GENERAL_ERR_START) { + hfi2_is_cport_int(dd, source); + return; + } + + dd_dev_err(dd, "unhandled misc interrupt %s\n", + jkr_is_misc_name(name, sizeof(name), source)); +} + +static void jkr_is_sdma_eng_err_int(struct hfi2_devdata *dd, + unsigned int source) +{ + hfi2_interrupt_clear_down(dd, source, &jkr_sdma_eng_err); +} + +static void jkr_is_various_int(struct hfi2_devdata *dd, unsigned int source) +{ + char name[64]; + + if (source == 3) { /* "TCritInt" */ + hfi2_handle_temp_err(dd); + return; + } + + /* not expecting any other various interrupts */ + dd_dev_err(dd, "unhandled various interrupt %s\n", + jkr_is_various_name(name, sizeof(name), source)); +} + +static void jkr_is_port_int(struct hfi2_devdata *dd, unsigned int source) +{ + char name[64]; + u32 pidx = source / 8; /* port interrupts are in groups of 8 */ + u32 which = source % 8; + + if (which == 4) { /* send egress errors */ + hfi2_interrupt_clear_down(dd, pidx, &jkr_send_egress_err); + return; + } + if (which == 5) { /* receive errors */ + hfi2_interrupt_clear_down(dd, pidx, &jkr_rcv_err); + return; + } + + dd_dev_err(dd, "unhandled port interrupt %s\n", + jkr_is_port_name(name, sizeof(name), source)); +} + +static void jkr_is_pcb_int(struct hfi2_devdata *dd, unsigned int source) +{ + char name[64]; + + /* + * This is a per-send context interrupt. It is called if the PbcIntr + * bit is set on a context's PIO PBC and the packet has completely + * cleared the send buffer. + * + * Presently, the PbcIntr bit is never set. + */ + dd_dev_err(dd, "unhandled pcb interrupt %s\n", + jkr_is_pcb_name(name, sizeof(name), source)); +} + +static void jkr_is_pio_err_int(struct hfi2_devdata *dd, unsigned int source) +{ + /* this is a per-SI interrupt */ + hfi2_interrupt_clear_down(dd, 0, &jkr_send_pio_err); +} + +static void jkr_is_sdma_err_si_int(struct hfi2_devdata *dd, unsigned int source) +{ + /* this is a per-SI interrupt */ + hfi2_interrupt_clear_down(dd, 0, &jkr_send_dma_err); +} + +static void jkr_is_csr_err_int(struct hfi2_devdata *dd, unsigned int source) +{ + /* this is a per-SI interrupt */ + hfi2_interrupt_clear_down(dd, 0, &jkr_csr_err); +} + +static void jkr_is_reserved_int(struct hfi2_devdata *dd, unsigned int source) +{ + char name[64]; + + dd_dev_err(dd, "unhandled reserved interrupt %s\n", + jkr_is_reserved_name(name, sizeof(name), source)); +} + +const struct hfi2_is_table hfi2_jkr_is_table[] = { + /* + * start end + * name func interrupt func + */ + { JKR_IS_GENERAL_ERR_START, JKR_IS_GENERAL_ERR_END, jkr_is_misc_name, + jkr_is_misc_int }, + { JKR_IS_SDMAENG_ERR_START, JKR_IS_SDMAENG_ERR_END, + hfi2_is_sdma_eng_err_name, jkr_is_sdma_eng_err_int }, + { JKR_IS_SENDCTXT_ERR_START, JKR_IS_SENDCTXT_ERR_END, + hfi2_is_sendctxt_err_name, hfi2_is_sendctxt_err_int }, + { JKR_IS_SDMA_START, JKR_IS_SDMA_IDLE_END, hfi2_is_sdma_eng_name, + hfi2_is_sdma_eng_int }, + { JKR_IS_VARIOUS_START, JKR_IS_VARIOUS_END, jkr_is_various_name, + jkr_is_various_int }, + { JKR_IS_PORT_START, JKR_IS_PORT_END, jkr_is_port_name, + jkr_is_port_int }, + { JKR_IS_RCVAVAIL_START, JKR_IS_RCVAVAIL_END, hfi2_is_rcv_avail_name, + hfi2_is_rcv_avail_int }, + { JKR_IS_RCVURGENT_START, JKR_IS_RCVURGENT_END, hfi2_is_rcv_urgent_name, + hfi2_is_rcv_urgent_int }, + { JKR_IS_SENDCREDIT_START, JKR_IS_SENDCREDIT_END, + hfi2_is_send_credit_name, hfi2_is_send_credit_int }, + { JKR_IS_PBC_START, JKR_IS_PBC_END, jkr_is_pcb_name, jkr_is_pcb_int }, + { JKR_IS_PIO_ERR_START, JKR_IS_PIO_ERR_END, jkr_is_pio_err_name, + jkr_is_pio_err_int }, + { JKR_IS_SDMA_ERR_SI_START, JKR_IS_SDMA_ERR_SI_END, + jkr_is_sdma_err_si_name, jkr_is_sdma_err_si_int }, + { JKR_IS_CSR_ERR_START, JKR_IS_CSR_ERR_END, jkr_is_csr_err_name, + jkr_is_csr_err_int }, + { JKR_IS_RESERVED_START, JKR_IS_RESERVED_END, jkr_is_reserved_name, + jkr_is_reserved_int }, + { 0, 0, 0, 0 } /* terminator */ +}; + +/* + * General interrupt sources to enable. This is all sources but SDMA + * (SdmaEngErr, Sdma, SdmaProgress, SdmaIdle), and Receive (RcvAvail, + * RcvUrgent). MctxtCportToPcieInt is enabled separately. + */ +const struct gi_enable_entry hfi2_jkr_gi_enable_table[] = { + { JKR_IS_GENERAL_ERR_START, JKR_ASIC_ERR_INT }, + { JKR_IS_SENDCTXT_ERR_START, JKR_IS_SENDCTXT_ERR_END }, + { JKR_IS_VARIOUS_START, JKR_IS_VARIOUS_END }, + { JKR_IS_PORT_START, JKR_IS_PORT_END }, + { JKR_IS_SENDCREDIT_START, JKR_IS_SENDCREDIT_END }, + { JKR_IS_PBC_START, JKR_IS_PBC_END }, + { JKR_IS_PIO_ERR_START, JKR_IS_PIO_ERR_END }, + { JKR_IS_SDMA_ERR_SI_START, JKR_IS_SDMA_ERR_SI_END }, + { JKR_IS_CSR_ERR_START, JKR_IS_CSR_ERR_END }, + { 1, 0 } /* terminator */ +}; + +void hfi2_jkr_set_port_tid_config(struct hfi2_devdata *dd, int pidx, u16 ctxt, + u32 eager_base, u16 alloced, + u32 expected_base, u32 expected_count) +{ + u64 reg; + + if (dd->is_vf) { + hfi2_vf2pf_tid_config(dd, pidx, ctxt, eager_base, alloced, + expected_base, expected_count); + return; + } + /* set eager count and base index */ + reg = ((u64)(alloced >> RCV_SHIFT) << RCV_EGR_CTRL_EGR_CNT_SHIFT) | + ((eager_base >> RCV_SHIFT) << RCV_EGR_CTRL_EGR_BASE_INDEX_SHIFT); + write_rctxt_csr(dd, ctxt, dd->params->rcv_egr_ctrl_reg, reg); + + /* + * Set TID (expected) count and base index. + * rcd->expected_count is set to individual RcvArray entries, + * not pairs, and the CSR takes a pair-count in groups of + * four, so divide by 8. + */ + reg = ((u64)(expected_count >> RCV_SHIFT) + << RCV_TID_CTRL_TID_PAIR_CNT_SHIFT) | + ((expected_base >> RCV_SHIFT) + << RCV_TID_CTRL_TID_BASE_INDEX_SHIFT); + write_rctxt_csr(dd, ctxt, dd->params->rcv_tid_ctrl_reg, reg); + + /* + * Value must match value written into RcvTidCtrl.TidPairCnt. See + * hfi2_rcvctrl() write to rcv_tid_ctrl_reg. + */ + reg = (u64)(expected_count >> RCV_SHIFT); + write_iprc_csr(dd, pidx, ctxt, JKR_RCV_TID_PAIR_COUNT, reg); + if (dd->is_sriov && pidx < dd->num_pports) + write_iprc_csr(dd, loopback_pidx_dd(dd, pidx), ctxt, + JKR_RCV_TID_PAIR_COUNT, reg); +} + +static inline u32 rcvarray_offset(u32 ctxt, u32 index, u32 type) +{ + return (type == PT_EAGER ? 0 : + BIT(JKR_RCV_ARRAY_EGR_TID_SELECT_SHIFT)) | + (ctxt_bar_ctxt(ctxt) << JKR_RCV_ARRAY_RCV_CTXT_IDX_SHIFT) | + (index << JKR_RCV_ARRAY_CSR_INDEX_SHIFT); +} + +static inline u8 __iomem *rcvarray_addr(struct hfi2_devdata *dd, u32 ctxt, + u32 index, u32 type) +{ + return dd->bar_maps[ctxt_bar_idx(ctxt)].rcvarray_wc + + rcvarray_offset(ctxt, index, type); +} + +/* + * Update a TID entry of a given receive context. + * + * @rcd Receive context being updated. + * @index When type is PT_EAGER or PT_EXPECTED, index is the index into the + * receive array _relative_ to how the context is set up. + * @pa Physical DMA address. If invalidating, this should be zero. + * @order Order of map. If invalidating, this should be zero. + * @flush Forced flush. Otherwise, will flush on eager or on 32-byte boundary. + */ +void hfi2_jkr_put_tid(struct hfi2_ctxtdata *rcd, u32 index, u32 type, + unsigned long pa, u16 order, bool flush) +{ + struct hfi2_devdata *dd = rcd->dd; + u64 reg; + u8 __iomem *addr; + + if (!(dd->flags & HFI2_PRESENT)) + return; + + trace_hfi2_put_tid(dd, index, type, pa, order); + addr = rcvarray_addr(dd, rcd->ctxt, index, type); + +#define RT_ADDR_SHIFT 12 /* 4KB kernel address boundary */ + /* eager and expected have the same layout */ + reg = RCV_ARRAY_RT_WRITE_ENABLE_SMASK | + ((u64)order << JKR_RCV_ARRAY_EGR_RT_BUF_SIZE_SHIFT) | + (pa >> RT_ADDR_SHIFT); + trace_hfi2_write_rcvarray(addr, reg); + writeq(reg, addr); + + if (type == PT_EAGER || flush || (index & 3) == 3) + flush_wc(); +} + +/* + * Write an "no-op" RcvArray entry. + * + * Called by the TID registration code to write to unused/unneeded RcvArray + * entries to fill out a write-combining buffer line. The HFI will ignore this + * write to the RcvArray entry. + */ +void hfi2_jkr_rcv_array_wc_fill(struct hfi2_ctxtdata *rcd, u32 index, u32 type) +{ + u8 __iomem *addr = rcvarray_addr(rcd->dd, rcd->ctxt, index, type); + + writeq(0, addr); + if ((index & 3) == 3) + flush_wc(); +} + +void hfi2_jkr_ena_rcv_ctxt(struct hfi2_devdata *dd, u8 pidx, u16 ctxt, + bool enable) +{ + u64 bits = JKR_RCV_PKT_CTRL_RCV_PORT_ENABLE_SMASK | + JKR_RCV_PKT_CTRL_CONTEXT_ENABLED_SMASK; + u64 reg; + + reg = read_iprc_csr(dd, pidx, ctxt, JKR_RCV_PKT_CTRL); + /* always clear the L2TypeEnable field */ + reg &= ~JKR_RCV_PKT_CTRL_L2_TYPE_ENABLE_MASK_SMASK; + if (enable) { + /* allow 16B and 9B L2 */ + reg |= bits | + (0xcull << JKR_RCV_PKT_CTRL_L2_TYPE_ENABLE_MASK_SHIFT); + } else { + reg &= ~bits; + } + write_iprc_csr(dd, pidx, ctxt, JKR_RCV_PKT_CTRL, reg); +} + +void hfi2_jkr_upd_rcv_hdr_size(struct hfi2_devdata *dd, u8 pidx, u16 ctxt, + u32 size) +{ + u64 reg; + + reg = read_iprc_csr(dd, pidx, ctxt, JKR_RCV_PKT_CTRL); + reg &= ~JKR_RCV_PKT_CTRL_HDR_SIZE_SMASK; + reg |= (u64)size << JKR_RCV_PKT_CTRL_HDR_SIZE_SHIFT; + write_iprc_csr(dd, pidx, ctxt, JKR_RCV_PKT_CTRL, reg); +} + +/* chip specific rcv context enable, disable */ +void hfi2_jkr_enable_rcv_context(struct hfi2_pportdata *ppd, u16 ctxt, + u64 *kctxt_ctrl, bool enable) +{ + struct hfi2_devdata *dd = ppd->dd; + + hfi2_priv_reg_op(dd, ppd->hw_pidx, ctxt, 0, RC_ENABLE_OP, enable); + + /* adjustments to KctxtCtrl */ + if (enable) + *kctxt_ctrl |= + JKR_RCV_KCTXT_CTRL_RECEIVE_CUT_THROUGH_DISABLE_SMASK; +} + +void hfi2_jkr_update_rcv_hdr_size(struct hfi2_pportdata *ppd, u16 ctxt, + u32 size) +{ + struct hfi2_devdata *dd = ppd->dd; + + hfi2_priv_reg_op(dd, ppd->hw_pidx, ctxt, 0, RC_HEADER_OP, size); +} + +void hfi2_jkr_set_rheq_addr(struct hfi2_devdata *dd, u16 ctxt, u64 dma_addr) +{ + write_kctxt_csr(dd, ctxt, JKR_RCV_ERR_ADDR, dma_addr); +} + +bool hfi2_jkr_check_synth_status(struct hfi2_devdata *dd) +{ + return false; +} + +void hfi2_jkr_update_synth_status(struct hfi2_devdata *dd) +{ +} + +#define FLAG_ENTRY1(flag, str) \ + { \ + flag, str, 0 \ + } +const struct flag_table hfi2_jkr_egress_err_info_flags[] = { + FLAG_ENTRY1(BIT_ULL(62), "PbcTestErr"), + FLAG_ENTRY1(BIT_ULL(61), "RawIPv6Err"), + FLAG_ENTRY1(BIT_ULL(60), "RawErr"), + FLAG_ENTRY1(BIT_ULL(59), "AgeCspecErr9B"), + FLAG_ENTRY1(BIT_ULL(58), "AgeCspecErr16B"), + FLAG_ENTRY1(BIT_ULL(57), "GRHErr9B"), + FLAG_ENTRY1(BIT_ULL(56), "GRHErr16B"), + FLAG_ENTRY1(BIT_ULL(55), "SdmaMemSpaceErr9B"), + FLAG_ENTRY1(BIT_ULL(54), "SdmaMemSpaceErr16B"), + FLAG_ENTRY1(BIT_ULL(53), "SdmaMemSpaceErr10B"), + FLAG_ENTRY1(BIT_ULL(52), "SdmaMemSpaceErr8B"), + FLAG_ENTRY1(BIT_ULL(51), "DisallowedPortErr9B"), + FLAG_ENTRY1(BIT_ULL(50), "DisallowedPortErr16B"), + FLAG_ENTRY1(BIT_ULL(49), "DisallowedPortErr10B"), + FLAG_ENTRY1(BIT_ULL(48), "DisallowedPortErr8B"), + FLAG_ENTRY1(BIT_ULL(47), "BadPktLenErr9B"), + FLAG_ENTRY1(BIT_ULL(46), "BadPktLenErr16B"), + FLAG_ENTRY1(BIT_ULL(45), "BadPktLenErr10B"), + FLAG_ENTRY1(BIT_ULL(44), "BadPktLenErr8B"), + FLAG_ENTRY1(BIT_ULL(43), "NonKDETHPacketErr9B"), + FLAG_ENTRY1(BIT_ULL(42), "NonKDETHPacketErr16B"), + FLAG_ENTRY1(BIT_ULL(41), "NonKDETHPacketErr10B"), + FLAG_ENTRY1(BIT_ULL(40), "NonKDETHPacketErr8B"), + FLAG_ENTRY1(BIT_ULL(39), "KDETHPacketErr9B"), + FLAG_ENTRY1(BIT_ULL(38), "KDETHPacketErr16B"), + FLAG_ENTRY1(BIT_ULL(37), "KDETHPacketErr10B"), + FLAG_ENTRY1(BIT_ULL(36), "KDETHPacketErr8B"), + FLAG_ENTRY1(BIT_ULL(35), "TooLongPacketErr9B"), + FLAG_ENTRY1(BIT_ULL(34), "TooLongPacketErr16B"), + FLAG_ENTRY1(BIT_ULL(33), "TooLongPacketErr10B"), + FLAG_ENTRY1(BIT_ULL(32), "TooLongPacketErr8B"), + FLAG_ENTRY1(BIT_ULL(31), "TooSmallPacketErr9B"), + FLAG_ENTRY1(BIT_ULL(30), "TooSmallPacketErr16B"), + FLAG_ENTRY1(BIT_ULL(29), "TooSmallPacketErr10B"), + FLAG_ENTRY1(BIT_ULL(28), "TooSmallPacketErr8B"), + FLAG_ENTRY1(BIT_ULL(27), "VLMappingErr9B"), + FLAG_ENTRY1(BIT_ULL(26), "VLMappingErr16B"), + FLAG_ENTRY1(BIT_ULL(25), "VLMappingErr10B"), + FLAG_ENTRY1(BIT_ULL(24), "VLMappingErr8B"), + FLAG_ENTRY1(BIT_ULL(23), "OpcodeErr9B"), + FLAG_ENTRY1(BIT_ULL(22), "OpcodeErr16B"), + FLAG_ENTRY1(BIT_ULL(21), "OpcodeErr10B"), + FLAG_ENTRY1(BIT_ULL(20), "OpcodeErr8B"), + FLAG_ENTRY1(BIT_ULL(19), "SLIDErr9B"), + FLAG_ENTRY1(BIT_ULL(18), "SLIDErr16B"), + FLAG_ENTRY1(BIT_ULL(17), "SLIDErr10B"), + FLAG_ENTRY1(BIT_ULL(16), "SLIDErr8B"), + FLAG_ENTRY1(BIT_ULL(15), "PartitionKeyErr9B"), + FLAG_ENTRY1(BIT_ULL(14), "PartitionKeyErr16B"), + FLAG_ENTRY1(BIT_ULL(13), "PartitionKeyErr10B"), + FLAG_ENTRY1(BIT_ULL(12), "PartitionKeyErr8B"), + FLAG_ENTRY1(BIT_ULL(11), "JobKeyErr9B"), + FLAG_ENTRY1(BIT_ULL(10), "JobKeyErr16B"), + FLAG_ENTRY1(BIT_ULL(9), "JobKeyErr10B"), + FLAG_ENTRY1(BIT_ULL(8), "JobKeyErr8B"), + FLAG_ENTRY1(BIT_ULL(7), "VLErr9B"), + FLAG_ENTRY1(BIT_ULL(6), "VLErr16B"), + FLAG_ENTRY1(BIT_ULL(5), "VLErr10B"), + FLAG_ENTRY1(BIT_ULL(4), "VLErr8B"), + FLAG_ENTRY1(BIT_ULL(3), "L2TypeErr9B"), + FLAG_ENTRY1(BIT_ULL(2), "L2TypeErr16B"), + FLAG_ENTRY1(BIT_ULL(1), "L2TypeErr10B"), + FLAG_ENTRY1(BIT_ULL(0), "L2TypeErr8B"), +}; + +const struct flag_data hfi2_jkr_egress_err_info_data = { + .table = hfi2_jkr_egress_err_info_flags, + .size = ARRAY_SIZE(hfi2_jkr_egress_err_info_flags), +}; + +/* send context base integrity checks */ +#define SC_BASE_CHECKS \ + (0 /* 9B */ \ + | JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BTOO_LONG_PACKET_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BTOO_SMALL_PACKETS_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BBAD_PKT_LEN_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BRAW_IPV6_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BRAW_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BPBC_TEST_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BVL_MAPPING_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BOPCODE_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BSLID_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BJOB_KEY_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BVL_SMASK /* 16B */ \ + | JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BTOO_LONG_PACKET_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BTOO_SMALL_PACKETS_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BBAD_PKT_LEN_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BRAW_IPV6_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BRAW_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BPBC_TEST_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK16BVL_MAPPING_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK16BSLID_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK16BJOB_KEY_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_CHECK16BVL_SMASK) + +/* send context user integrity checks */ +#define SC_USER_CHECKS \ + (0 /* 9B */ \ + | JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BNON_KDETH_PACKETS_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BGRH_SMASK /* 16B */ \ + | JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BNON_KDETH_PACKETS_SMASK | \ + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BGRH_SMASK) + +/* send context kernel integrity checks */ +#define SC_KERNEL_CHECKS \ + (0 /* 9B */ \ + | JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BKDETH_PACKETS_SMASK /* 16B */ \ + | JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BKDETH_PACKETS_SMASK) + +static void jkr_set_pio_integ(struct hfi2_devdata *dd, u32 pidx, u32 hw_context, + int type, enum spi_cmds cmd) +{ + u64 val; + + /* DEFAULT does not do a read-modify-write */ + if (cmd == SPI_DEFAULT) { + /* allow 9B and 16B packets, no checking */ + val = JKR_SEND_CTXT_CHECK_ENABLE_L2_TYPE9BALLOWED_SMASK | + JKR_SEND_CTXT_CHECK_ENABLE_L2_TYPE16BALLOWED_SMASK; + } else { + val = read_epsc_csr(dd, pidx, hw_context, + dd->params->send_ctxt_check_enable_reg); + } + + switch (cmd) { + case SPI_DEFAULT: + /* No integrity checks if HFI2_CAP_NO_INTEGRITY is set */ + if (HFI2_CAP_IS_KSET(NO_INTEGRITY)) + break; + val |= SC_BASE_CHECKS; + if (type == SC_USER) + val |= SC_USER_CHECKS; + else if (type != SC_KERNEL) + val |= SC_KERNEL_CHECKS; + break; + case SPI_INIT: + /* no checks to set/clear */ + break; + case SPI_SET_JKEY: + val |= JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BJOB_KEY_SMASK | + JKR_SEND_CTXT_CHECK_ENABLE_CHECK16BJOB_KEY_SMASK; + break; + case SPI_CLEAR_JKEY: + val &= ~(JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BJOB_KEY_SMASK | + JKR_SEND_CTXT_CHECK_ENABLE_CHECK16BJOB_KEY_SMASK); + break; + case SPI_SET_PKEY: + val |= JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BPARTITION_KEY_SMASK | + JKR_SEND_CTXT_CHECK_ENABLE_CHECK16BPARTITION_KEY_SMASK; + + val &= ~( + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW9BKDETH_PACKETS_SMASK | + JKR_SEND_CTXT_CHECK_ENABLE_DISALLOW16BKDETH_PACKETS_SMASK); + break; + case SPI_CLEAR_PKEY: + val &= ~( + JKR_SEND_CTXT_CHECK_ENABLE_CHECK9BPARTITION_KEY_SMASK | + JKR_SEND_CTXT_CHECK_ENABLE_CHECK16BPARTITION_KEY_SMASK); + break; + } + write_epsc_csr(dd, pidx, hw_context, + dd->params->send_ctxt_check_enable_reg, val); +} + +void hfi2_jkr_set_pio_integrity(struct hfi2_devdata *dd, u32 pidx, + u32 hw_context, int type, enum spi_cmds cmd) +{ + jkr_set_pio_integ(dd, pidx, hw_context, type, cmd); + if (dd->is_sriov) + jkr_set_pio_integ(dd, loopback_pidx_dd(dd, pidx), hw_context, + type, cmd); +} + +void hfi2_jkr_read_link_quality(struct hfi2_pportdata *ppd, u8 *link_quality) +{ + *link_quality = 5; /* best */ +} + +void hfi2_jkr_handle_link_bounce(struct work_struct *work) +{ + struct hfi2_pportdata *ppd = + container_of(work, struct hfi2_pportdata, link_bounce_work); + + if (ppd->host_link_state & HLS_UP) { + hfi2_set_link_state(ppd, HLS_DN_OFFLINE); + hfi2_start_link(ppd); + } else { + ppd_dev_info(ppd, "%s: link not up (%s), nothing to do\n", + __func__, + hfi2_link_state_name(ppd->host_link_state)); + } +} diff --git a/drivers/infiniband/hw/hfi2/exp_rcv.h b/drivers/infiniband/hw/hfi2/exp_rcv.h new file mode 100644 index 000000000000..41b13410d696 --- /dev/null +++ b/drivers/infiniband/hw/hfi2/exp_rcv.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright(c) 2017 Intel Corporation. + * Copyright(c) 2025-2026 Cornelis Networks, Inc. + */ + +#ifndef _HFI2_EXP_RCV_H +#define _HFI2_EXP_RCV_H +#include "hfi2.h" + +#define EXP_TID_SET_EMPTY(set) (set.count == 0 && list_empty(&set.list)) + +#define EXP_TID_TIDLEN_MASK 0x7FFULL +#define EXP_TID_TIDLEN_SHIFT 0 +#define EXP_TID_TIDCTRL_MASK 0x3ULL +#define EXP_TID_TIDCTRL_SHIFT 20 +#define EXP_TID_TIDIDX_MASK 0x3FFULL +#define EXP_TID_TIDIDX_SHIFT 22 + +/* Expected buffer entry (TID) addressing supported by the hardware */ +#define EXP_TID_ADDR_SHIFT 12 +#define EXP_TID_ADDR_SIZE BIT(EXP_TID_ADDR_SHIFT) + +#define EXP_TID_GET(tid, field) \ + (((tid) >> EXP_TID_TID##field##_SHIFT) & EXP_TID_TID##field##_MASK) + +#define EXP_TID_SET(field, value) \ + (((value) & EXP_TID_TID##field##_MASK) << \ + EXP_TID_TID##field##_SHIFT) +#define EXP_TID_CLEAR(tid, field) ({ \ + (tid) &= ~(EXP_TID_TID##field##_MASK << \ + EXP_TID_TID##field##_SHIFT); \ + }) +#define EXP_TID_RESET(tid, field, value) do { \ + EXP_TID_CLEAR(tid, field); \ + (tid) |= EXP_TID_SET(field, (value)); \ + } while (0) + +/* + * Define fields in the KDETH header so we can update the header + * template. + */ +#define KDETH_OFFSET_SHIFT 0 +#define KDETH_OFFSET_MASK 0x7fff +#define KDETH_OM_SHIFT 15 +#define KDETH_OM_MASK 0x1 +#define KDETH_TID_SHIFT 16 +#define KDETH_TID_MASK 0x3ff +#define KDETH_TIDCTRL_SHIFT 26 +#define KDETH_TIDCTRL_MASK 0x3 +#define KDETH_INTR_SHIFT 28 +#define KDETH_INTR_MASK 0x1 +#define KDETH_SH_SHIFT 29 +#define KDETH_SH_MASK 0x1 +#define KDETH_KVER_SHIFT 30 +#define KDETH_KVER_MASK 0x3 +#define KDETH_JKEY_SHIFT 0x0 +#define KDETH_JKEY_MASK 0xff +#define KDETH_HCRC_UPPER_SHIFT 16 +#define KDETH_HCRC_UPPER_MASK 0xff +#define KDETH_HCRC_LOWER_SHIFT 24 +#define KDETH_HCRC_LOWER_MASK 0xff + +#define KDETH_GET(val, field) \ + (((le32_to_cpu((val))) >> KDETH_##field##_SHIFT) & KDETH_##field##_MASK) +#define KDETH_SET(dw, field, val) do { \ + u32 dwval = le32_to_cpu(dw); \ + dwval &= ~(KDETH_##field##_MASK << KDETH_##field##_SHIFT); \ + dwval |= (((val) & KDETH_##field##_MASK) << \ + KDETH_##field##_SHIFT); \ + dw = cpu_to_le32(dwval); \ + } while (0) + +#define KDETH_RESET(dw, field, val) ({ dw = 0; KDETH_SET(dw, field, val); }) + +/* KDETH OM multipliers and switch over point */ +#define KDETH_OM_SMALL 4 +#define KDETH_OM_SMALL_SHIFT 2 +#define KDETH_OM_LARGE 64 +#define KDETH_OM_LARGE_SHIFT 6 +#define KDETH_OM_MAX_SIZE (1 << ((KDETH_OM_LARGE / KDETH_OM_SMALL) + 1)) + +struct tid_group { + struct list_head list; + u32 base; + u8 size; + u8 used; + u8 map; +}; + +static inline void tid_group_add_tail(struct tid_group *grp, + struct exp_tid_set *set) +{ + list_add_tail(&grp->list, &set->list); + set->count++; +} + +static inline void tid_group_remove(struct tid_group *grp, + struct exp_tid_set *set) +{ + list_del_init(&grp->list); + set->count--; +} + +static inline void tid_group_move(struct tid_group *group, + struct exp_tid_set *s1, + struct exp_tid_set *s2) +{ + tid_group_remove(group, s1); + tid_group_add_tail(group, s2); +} + +static inline struct tid_group *tid_group_pop(struct exp_tid_set *set) +{ + struct tid_group *grp = + list_first_entry(&set->list, struct tid_group, list); + list_del_init(&grp->list); + set->count--; + return grp; +} + +static inline u32 create_tid(u32 rcventry, u32 npages) +{ + u32 pair = rcventry & ~0x1; + + return EXP_TID_SET(IDX, pair >> 1) | + EXP_TID_SET(CTRL, 1 << (rcventry - pair)) | + EXP_TID_SET(LEN, npages); +} + +/** + * hfi2_tid_group_to_idx - convert an index to a group + * @rcd - the receive context + * @grp - the group pointer + */ +static inline u16 +hfi2_tid_group_to_idx(struct hfi2_ctxtdata *rcd, struct tid_group *grp) +{ + return grp - &rcd->groups[0]; +} + +/** + * hfi2_idx_to_tid_group - convert a group to an index + * @rcd - the receive context + * @idx - the index + */ +static inline struct tid_group * +hfi2_idx_to_tid_group(struct hfi2_ctxtdata *rcd, u16 idx) +{ + return &rcd->groups[idx]; +} + +int hfi2_alloc_ctxt_rcv_groups(struct hfi2_ctxtdata *rcd); +void hfi2_free_ctxt_rcv_groups(struct hfi2_ctxtdata *rcd); +void hfi2_exp_tid_group_init(struct hfi2_ctxtdata *rcd); + +#endif /* _HFI2_EXP_RCV_H */ diff --git a/drivers/infiniband/hw/hfi2/sriov.c b/drivers/infiniband/hw/hfi2/sriov.c new file mode 100644 index 000000000000..1c9d82065c60 --- /dev/null +++ b/drivers/infiniband/hw/hfi2/sriov.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause +/* + * Copyright(c) 2025-2026 Cornelis Networks, Inc. + * + * SR-IOV related functions and variables. + */ + +#include <linux/pci.h> + +#include "hfi2.h" +#include "sriov.h" +#include "chip_jkr.h" +#include "chip_gen.h" +#include "vf2pf.h" + +#define HFI_SRIOV_DEBUG +#define HFI_SRIOV_BRINGUP +#define HFI_SRIOV_MOD_PARAMS /* use module params to define SRIOV config */ +#undef HFI_SRIOV_AUTO_CONF /* automatically enable SRIOV if max_num_vfs > 0 */ + +static bool sriov_auto; /* might default to 'true' in the future */ + +#ifdef HFI_SRIOV_BRINGUP +static bool vf_test; + +static bool vf_claim; +#endif + +#ifdef HFI_SRIOV_MOD_PARAMS +static uint si_idx; +static uint max_num_vfs; +static uint ctxt_per_vf; /* send/recv use same number */ +static uint sdma_per_vf; +static uint rcv_per_vf; +static uint pio_per_vf; +#endif + +#define HFI_MIN_PF0_CONTEXTS 32 /* includes max used by CPORT */ +#define HFI_MIN_PF0_SDE 2 + +/* + * Only called on PF0, but possibly on behalf of VF. + * + * 'dd' is the PF0, 'out' is the target based on 'si'. + * 'out' is assumed to be uninitialized. + */ +int hfi2_sriov_get_config(struct hfi2_devdata *dd, struct hfi2_devrsrcs *out, int si) +{ + u32 num_ctxt, pf0_ctxts, vf2pf_ctxts; + u32 num_sdma; + u32 num_rcvary; + u32 num_pio; + int num_vfs = 0; + int num; + +#ifdef HFI_SRIOV_MOD_PARAMS + num_vfs = max_num_vfs; +#endif + /* TODO: other methods of SRIOV config... */ + + if (!num_vfs) + return -ENODEV; + + if (si > JKR_C_CCE_NUM_VFS || num_vfs > JKR_C_CCE_NUM_VFS) + return -EINVAL; + +#ifdef HFI_SRIOV_MOD_PARAMS + /* + * Assumes #send contexts == #recv contexts, uses #recv + * as the limit since #send might be larger in the future. + */ + vf2pf_ctxts = hfi2_vf2pf_num_ctxts(dd); + num_ctxt = chip_rcv_contexts(dd) - vf2pf_ctxts; + num_sdma = chip_sdma_engines(dd); + num_rcvary = chip_rcv_array_count(dd) - HFI_MIN_PF0_RCVARY(vf2pf_ctxts); + num_pio = chip_pio_mem_size(dd) / PIO_BLOCK_SIZE - + HFI_MIN_PF0_PIO(vf2pf_ctxts); + + /* + * Automatically adjust excessive values and set defaults for '0'. + * This may not work well if adapters of different architecture are + * installed (and having SRIOV capability). + */ + if (!ctxt_per_vf) + ctxt_per_vf = (num_ctxt - HFI_MIN_PF0_CONTEXTS) / max_num_vfs; + if (ctxt_per_vf * max_num_vfs > num_ctxt - HFI_MIN_PF0_CONTEXTS) { + ctxt_per_vf = (num_ctxt - HFI_MIN_PF0_CONTEXTS) / max_num_vfs; + dd_dev_info(dd, "Reducing ctxt_per_vf to %d\n", ctxt_per_vf); + } + pf0_ctxts = num_ctxt - ctxt_per_vf * max_num_vfs; + + if (!sdma_per_vf) + sdma_per_vf = (num_sdma - HFI_MIN_PF0_SDE) / max_num_vfs; + if (!rcv_per_vf) + rcv_per_vf = (num_rcvary - HFI_MIN_PF0_RCVARY(pf0_ctxts)) / + max_num_vfs; + if (!pio_per_vf) + pio_per_vf = + (num_pio - HFI_MIN_PF0_PIO(pf0_ctxts)) / max_num_vfs; + + if (sdma_per_vf * max_num_vfs > num_sdma - HFI_MIN_PF0_SDE) { + sdma_per_vf = (num_sdma - HFI_MIN_PF0_SDE) / max_num_vfs; + dd_dev_info(dd, "Reducing sdma_per_vf to %d\n", sdma_per_vf); + } + if (rcv_per_vf * max_num_vfs > + num_rcvary - HFI_MIN_PF0_RCVARY(pf0_ctxts)) { + rcv_per_vf = (num_rcvary - HFI_MIN_PF0_RCVARY(pf0_ctxts)) / + max_num_vfs; + dd_dev_info(dd, "Reducing rcv_per_vf to %d\n", rcv_per_vf); + } + if (pio_per_vf * max_num_vfs > num_pio - HFI_MIN_PF0_PIO(pf0_ctxts)) { + pio_per_vf = + (num_pio - HFI_MIN_PF0_PIO(pf0_ctxts)) / max_num_vfs; + dd_dev_info(dd, "Reducing pio_per_vf to %d\n", pio_per_vf); + } + + out->num_vfs = max_num_vfs; + out->si_idx = si; + out->pfunit = dd->unit; + /* si_idx == 0 implies PF0, which decides all VF resources */ + + num = si ? si : max_num_vfs + 1; /* never 0 */ + out->c.first_send_context = si ? num_ctxt - num * ctxt_per_vf : 0; + out->c.last_send_context = num_ctxt - (num - 1) * ctxt_per_vf; + out->c.first_rcv_context = si ? num_ctxt - num * ctxt_per_vf : 0; + out->c.last_rcv_context = num_ctxt - (num - 1) * ctxt_per_vf; + out->first_sdma_engine = si ? num_sdma - num * sdma_per_vf : 0; + out->last_sdma_engine = num_sdma - (num - 1) * sdma_per_vf; + out->c.first_rcvarray_entry = si ? num_rcvary - num * rcv_per_vf : 0; + out->c.last_rcvarray_entry = num_rcvary - (num - 1) * rcv_per_vf; + out->c.first_pio_block = si ? num_pio - num * pio_per_vf : 0; + out->c.last_pio_block = num_pio - (num - 1) * pio_per_vf; +#endif + return 0; +} + +/* + * If SRIOV is allowed (max_num_vfs > 0) then divide up + * resources according to heuristics or limits. The rest of the driver + * init should use these parameters, if available (dd->rsrcs.num_vfs != 0). + * Note that the driver can no longer assume a resource begins at "0". + */ +int hfi2_sriov_set_cfg(struct hfi2_devdata *dd) +{ + int ret; + + if (dd->params->chip_type == CHIP_WFR) + return 0; + /* prior call to hfi2_sriov_set_si() must have succeeded */ + if (dd->is_vf) { + ret = hfi2_vf2pf_get_config(dd, &dd->rsrcs, dd->rsrcs.si_idx); + return ret; + } + if (!hfi2_sriov_is_enabled()) + return 0; + + ret = hfi2_sriov_get_config(dd, &dd->rsrcs, 0); + return ret; +} + +int hfi2_sriov_set_si(struct hfi2_devdata *dd) +{ + int si = 0; /* assume PF0 to start */ + + if (!dd->is_vf) + goto out_set; + + si = si_idx; + if (!si) { + if (dd->is_vm) + si = hfi2_vf2pf_probe_si(dd); /* hope for the best */ + else + /* TODO: need to adjust for CYR? does pci_iov already do that? */ + si = pci_iov_vf_id(dd->pcidev) + 1; + } + if (!si) { + dd_dev_err(dd, "Cannot determine device SI\n"); + return -ENXIO; + } +out_set: + dd->rsrcs.si_idx = si; + return 0; +} + +/* + * Sets the SiIdx CSRs of contrexts and SDMA engines for + * VF1..VFn based on data passed in 'dr'. + * + * Only PF0 can access the SiIdx CSRS, so any other callers + * just return success. + * + * Called on behalf of a VF when it makes first contact with PF0. + */ +int hfi2_sriov_assign_rsrcs(struct hfi2_devdata *dd, struct hfi2_devrsrcs *dr) +{ + int x; + + if (dd->is_vf) + return hfi2_vf2pf_assign_rsrcs(dd, dr); + + /* Only valid on PF0, if SRIOV is allowed */ + if (!dd->rsrcs.num_vfs || dd->rsrcs.si_idx) + return 0; + + /* + * In theory, resources allocated to PF0 never need to have their + * SiIdx written. + */ + if (!dr->si_idx) + return 0; + + for (x = dr->c.first_send_context; x < dr->c.last_send_context; ++x) + write_tctxt_csr(dd, x, JKR_SEND_CTXT_SI_IDX, dr->si_idx); + for (x = dr->c.first_rcv_context; x < dr->c.last_rcv_context; ++x) + write_rctxt_csr(dd, x, JKR_RCV_SI_IDX, dr->si_idx); + for (x = dr->first_sdma_engine; x < dr->last_sdma_engine; ++x) + write_sdmacfg_csr(dd, x, JKR_SEND_DMA_CFG_SI_IDX, dr->si_idx); + return 0; +} + +/* + * Resets the SiIdx CSRs of contrexts and SDMA engines for + * VF1..VFn back to PF0. + * + * Only PF0 can access the SiIdx CSRS, so any other callers + * just return success. + * + * Called on behalf of a VF when it disconnects with PF0. + */ +void hfi2_sriov_free_rsrcs(struct hfi2_devdata *dd, struct hfi2_devrsrcs *dr) +{ + int x; + + if (dd->is_vf) { + hfi2_vf2pf_free_rsrcs(dd, dr); + return; + } + /* Only valid on PF0, if SRIOV is allowed */ + if (!dd->rsrcs.num_vfs || dd->rsrcs.si_idx) + return; + + /* + * In theory, resources allocated to PF0 never need to have their + * SiIdx written. + */ + if (!dr->si_idx) + return; + + for (x = dr->c.first_send_context; x < dr->c.last_send_context; ++x) + write_tctxt_csr(dd, x, JKR_SEND_CTXT_SI_IDX, 0); + for (x = dr->c.first_rcv_context; x < dr->c.last_rcv_context; ++x) + write_rctxt_csr(dd, x, JKR_RCV_SI_IDX, 0); + for (x = dr->first_sdma_engine; x < dr->last_sdma_engine; ++x) + write_sdmacfg_csr(dd, x, JKR_SEND_DMA_CFG_SI_IDX, 0); +} + +/* + * Free all SRIOV configuration resources. Called during + * driver unload. + * + * Resets SiIdx CSRs as well as freeing memory. + */ +void hfi2_sriov_free_cfg(struct hfi2_devdata *dd) +{ + /* On PF0, set all SiIdx back to 0 */ + hfi2_sriov_free_rsrcs(dd, &dd->rsrcs); +} + +/* NOTE: + * There are three types of PCI devices when using SRIOV and VMs: + * in host OS, there is the PF0 pci_dev (pdev->is_physfn != 0) + * in host OS, there is the VFx pci_dev (pdev->is_virtfn != 0) + * (this will never have a 'dd'?) + * in guest OS, there is the VFx pci_dev (PCI_FUNC(pdev->devfn) != 0?) + * (what do pdev->is_physfn and pdev->is_virtfn mean? + * since pdev->physfn cannot be valid - nor pdev->sriov?) + * (this needs to have a 'dd' but handled different) + * + * TODO: how to determine whether we're host or guest?! + * PCI_FUNC(pdev->devfn) != 0 && !pdev->is_virtfn for guest VFs? + * + * TODO: does PCI_FUNC() need to be something different if ARI is active? + */ + +int hfi2_sriov_is_enabled(void) +{ +#ifdef HFI_SRIOV_MOD_PARAMS + return max_num_vfs > 0; +#else + /* TODO: how to determine SRIOV is allowed */ + return 0; +#endif +} + +/* + * This initializes the host VF PCI device - not SRIOV from PF0. + * + * Normally, the VF devices will be pass-through to VMs, in which + * case the host driver does not want to claim the device. + * TODO: Does KVM (virtsh) tolerate the driver claiming the VF, + * by calling the remove_one() function when assigning the device + * to the guest? If so, we can go ahead and claim the device here, + * however that will cause a complete setup (and tear-down) of a 'dd' for it. + * + * pdev is the VF. Only called from host driver. + */ +int hfi2_sriov_init(struct pci_dev *pdev) +{ + int ret; + +#ifdef CONFIG_HFI_L8SIM + ret = sim_sriov_fixup(pdev); + if (ret) + dev_warn(&pdev->dev, "SRIOV simpci fixup failed %d\n", ret); + /* continue, even though it probably won't work */ +#endif +#ifdef HFI_SRIOV_BRINGUP + if (vf_test) { + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "SRIOV pci enable failed %d\n", + ret); + return ret; + } + pci_set_master(pdev); + } + ret = vf_claim ? 0 : -ENODEV; +#else + ret = -ENODEV; +#endif + /* + * If not claimed then remove_one()/hfi2_sriov_remove() will never be called. + */ + + /* pci_num_vf(pdev->physfn) is not valid until later, so can't use it */ +#ifdef HFI_SRIOV_DEBUG + dev_info(&pdev->dev, "probing VF%d (%d)\n", pci_iov_vf_id(pdev) + 1, + ret); + dev_warn(&pdev->dev, + "is_vm=%d is_vf=%d is_physfn=%d is_virtfn=%d physfn=%p\n", +#if defined(CONFIG_X86) + boot_cpu_has(X86_FEATURE_HYPERVISOR), +#else + -1, +#endif + !pdev->pm_cap, pdev->is_physfn, pdev->is_virtfn, pdev->physfn); +#endif + + return ret; +} + +void hfi2_sriov_remove(struct pci_dev *pdev) +{ +#ifdef HFI_SRIOV_DEBUG + dev_info(&pdev->dev, "removing VF%d\n", pci_iov_vf_id(pdev) + 1); +#endif +} + +/* + * This disables SRIOV from PF0, if it was enabled. + * + * pdev is PF0. The driver is about to release this PF0. + */ +int hfi2_sriov_disable(struct pci_dev *pdev) +{ + pci_disable_sriov(pdev); + return 0; +} + +/* + * Deconfigure SRIOV on PF0. The driver may continue to run on PF0. + */ +static int hfi2_sriov_deconfigure(struct hfi2_devdata *dd) +{ + /* TODO: do paranoid cleanup? */ + pci_disable_sriov(dd->pcidev); + hfi2_pf0_cleanup(dd); + return 0; +} + +/* TODO: any setup required for RPMSG, etc. + * + * pdev is PF0. + */ +int hfi2_sriov_configure(struct pci_dev *pdev, int nvf) +{ + struct hfi2_devdata *dd = pci_get_drvdata(pdev); + int ret; + + if (!nvf) + return hfi2_sriov_deconfigure(dd); + + if (nvf > max_num_vfs) + return -EINVAL; + + /* prepare VF resources (contexts) for creation of VFs */ + ret = hfi2_vf2pf_prep(dd); + if (ret) + return ret; + + ret = pci_enable_sriov(pdev, nvf); + if (ret < 0) { + hfi2_sriov_deconfigure(dd); + return ret; + } + return nvf; +} + +/* + * Enables SRIOV if max_num_vfs > 0. + * + * Called at the very end of PF0 initialization (init_one()). + */ +int hfi2_sriov_auto_conf(struct hfi2_devdata *dd) +{ + int ret = 0; + + if (!max_num_vfs) + return ret; + + if (sriov_auto) { + ret = hfi2_sriov_configure(dd->pcidev, max_num_vfs); + if (ret) + dd_dev_err(dd, + "hfi2_sriov_configure(%d) failed (%d).\n", + max_num_vfs, ret); + } + return ret; +} diff --git a/drivers/infiniband/hw/hfi2/vf2pf.c b/drivers/infiniband/hw/hfi2/vf2pf.c new file mode 100644 index 000000000000..4a7e1135038c --- /dev/null +++ b/drivers/infiniband/hw/hfi2/vf2pf.c @@ -0,0 +1,1106 @@ +// SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause +/* + * Copyright(c) 2025-2026 Cornelis Networks, Inc. + * + * SRIOV support for VFs making requests to PF0. + */ + +#include "hfi2.h" +#include "chip.h" +#include "chip_gen.h" +#include "mad.h" +#include "sriov.h" +#include "vf2pf_int.h" + +#ifdef HFI_VF2PF_LOOPBACK +#include "vf2pf_lb.h" +#define HFI_VF2PF_LOOPBACK_CONFIG +#endif + +static bool vf2pf_lb = true; + +static uint vf2pf_to = 1; + +#define VF2PF_FORCE_LB /* set to force use of hfi2_loopback vf2pf even if VFs are local */ + +#ifdef VF2PF_FORCE_LB +#define IS_LOCAL_VF(dd) (!vf2pf_lb && !(dd)->is_vm) +#define IS_LOCAL_VDD(vdd) (!vf2pf_lb && (vdd)) +#else +#define IS_LOCAL_VF(dd) (!(dd)->is_vm) +#define IS_LOCAL_VDD(vdd) (vdd) +#endif + +static struct vf2pf_devops vf2pf_nodev = {}; + +static struct vf2pf_devops *vf2pf_dev = &vf2pf_nodev; + +/* for additional output to "hw_resources" */ +int hfi2_vf2pf_sysfs_emit_at(struct hfi2_devdata *dd, char *buf, int at) +{ + int off = at; + + /* + * Anything for vf2pf core goes here. + */ + + if (vf2pf_dev->sysfs_emit_at) + off += vf2pf_dev->sysfs_emit_at(dd, buf, off); + + return off - at; +} + +/* + * Allocate memory for a vf2pf message to transmit. + * Returns pointer to allocation, to be used in kfree() and + * passing to vf2pf_devops.send(). + * + * On success, + * 'msg' is set to struct vf2pf_hdr (vf2pf payload) part of allocation, + * + * buffer contents/structure: + * + * ret-> struct vf2pf_prefix + * -align u64- + * [opt: implimentation headers] + * msg-> struct vf2pf_hdr + * variable payload... + */ +static void *msg_alloc(struct hfi2_devdata *dd, struct vf2pf_hdr **msg) +{ + if (!vf2pf_dev->msg_alloc) + return NULL; + + return vf2pf_dev->msg_alloc(dd, msg); +} + +/* + * Send a message to 'si'. + * + * 'buf' is opaque pointer returned by msg_alloc(). + * header part (vf2pf_dev->get_msg(dd, buf)) must have been filled out. + * caller may kfree on return. + */ +static int vf2pf_send(struct hfi2_devdata *dd, u8 si, void *buf) +{ + if (!vf2pf_dev->send) + return -ENXIO; + + return vf2pf_dev->send(dd, si, buf); +} + +/* + * overwrites 'buf' with response. + * caller acquired 'buf' via msg_alloc(). + * on success, 'buf' contains the response (caller kfrees when done). + */ +static int vf2pf_send_recv(struct hfi2_devdata *dd, u8 si, void *buf, long to) +{ + struct vf2pf_prefix *pfx = buf; + struct vf2pf_hdr *hdr; + int ret; + + if (!vf2pf_dev->set_tid || !vf2pf_dev->get_msg) + return -EINVAL; + + if (to > 0) { + pfx->type = VF2PF_PFX_TYPE_WAIT; + init_waitqueue_head(&pfx->wait); + } else { + to = -to; + pfx->type = VF2PF_PFX_TYPE_SEMA; + sema_init(&pfx->sema, 0); + } + hdr = vf2pf_dev->get_msg(dd, buf); + hdr->tid = vf2pf_dev->set_tid(dd, buf); + + ret = vf2pf_send(dd, si, buf); + if (ret) { + vf2pf_dev->get_tid(dd, hdr->tid); /* discard tid */ + return ret; + } + + if (pfx->type == VF2PF_PFX_TYPE_WAIT) { + if (vf2pf_dev->rcv_wait) { + ret = vf2pf_dev->rcv_wait(dd, buf, to); + } else { + ret = wait_event_timeout(pfx->wait, + (hdr->op & VF2PF_OP_RESP), to); + ret = ret ? 0 : + -ETIME; /* convert residual time to error */ + } + } else { + ret = down_timeout(&pfx->sema, to); + } + if (ret) /* timeout or other error */ + vf2pf_dev->get_tid(dd, hdr->tid); /* discard tid */ + + return ret; +} + +/* + * VF call to PF0 to setup dd->rsrcs. + */ +int hfi2_vf2pf_get_config(struct hfi2_devdata *dd, struct hfi2_devrsrcs *out, int si) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_getcfg_msg *msg; + void *mem; + int ret; + + if (!dd->is_vf) + return -EINVAL; + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + ret = hfi2_sriov_get_config(pdd, out, si); + if (ret) + return ret; + dd->base_guid = pdd->base_guid; + dd->revision = pdd->revision; + dd->hfi2_id = pdd->hfi2_id; + dd->icode = pdd->icode; + dd->irev = pdd->irev; + dd->cport_ver = pdd->cport_ver; + return 0; + } + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct vf2pf_getcfg_msg *)hdr; + msg->hdr.op = VF2PF_GET_CFG; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + msg->si = si; + ret = vf2pf_send_recv(dd, 0, mem, vf2pf_to * HZ); + if (!ret) + ret = hdr->status; + if (ret) + goto out; + memcpy(out, &msg->rsrcs, sizeof(*out)); + dd->base_guid = msg->base_guid; + dd->revision = msg->revision; + dd->hfi2_id = msg->hfi2_id; + dd->icode = msg->icode; + dd->irev = msg->irev; + dd->cport_ver = msg->cport_ver; +out: + kfree(mem); + return ret; +} + +static int do_asgnrs_msg(struct hfi2_devdata *dd, u8 op, + struct hfi2_devrsrcs *vfr) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_asgnrs_msg *msg; + void *mem; + int ret; + + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct vf2pf_asgnrs_msg *)hdr; + msg->hdr.op = op; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + memcpy(&msg->rsrcs, vfr, sizeof(msg->rsrcs)); + ret = vf2pf_send_recv(dd, 0, mem, vf2pf_to * HZ); + if (!ret) + ret = hdr->status; + kfree(mem); + return ret; +} + +/* + * VF call to PF0 to assign chip resources to this SI. + * May include additional early setup. + */ +int hfi2_vf2pf_assign_rsrcs(struct hfi2_devdata *dd, struct hfi2_devrsrcs *vfr) +{ + if (!dd->is_vf) + return -EINVAL; + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + return hfi2_sriov_assign_rsrcs(pdd, vfr); + } + return do_asgnrs_msg(dd, VF2PF_ASGN_RES, vfr); +} + +/* + * VF call to PF0 to release chip resources. + * May include other late shutdown. + */ +int hfi2_vf2pf_free_rsrcs(struct hfi2_devdata *dd, struct hfi2_devrsrcs *vfr) +{ + if (!dd->is_vf) + return -EINVAL; + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + hfi2_sriov_free_rsrcs(pdd, vfr); + return 0; + } + return do_asgnrs_msg(dd, VF2PF_FREE_RES, vfr); +} + +int hfi2_vf2pf_priv_reg_op(struct hfi2_devdata *dd, int pidx, u32 ctxt, int type, + enum preg_op op, u64 arg) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_pregop_msg *msg; + void *mem; + int ret; + + if (!dd->is_vf) + return -EINVAL; + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + return hfi2_priv_reg_op(pdd, pidx, ctxt, type, op, arg); + } + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct vf2pf_pregop_msg *)hdr; + msg->hdr.op = VF2PF_PREG_OP; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + msg->pidx = pidx; + msg->ctxt = ctxt; + msg->type = type; + msg->op = op; + msg->arg = arg; + ret = vf2pf_send_recv(dd, 0, mem, vf2pf_to * HZ); + if (!ret) + ret = hdr->status; + kfree(mem); + return ret; +} + +/* Called for PF0 and VFs */ +u64 hfi2_pf0_read_csr(struct hfi2_devdata *dd, enum csr_type type, u32 off, u16 ctxt, + u8 pidx_eng) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_readcsr_msg *msg; + void *mem; + u64 reg = ~(u64)0; /* error */ + int ret; + + if (!dd->is_vf) + return hfi2_read_csr(dd, off); + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + return hfi2_read_csr_type(pdd, type, off, ctxt, pidx_eng); + } + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct vf2pf_readcsr_msg *)hdr; + msg->hdr.op = VF2PF_RCSR_OP; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + msg->off = off; + ret = vf2pf_send_recv(dd, 0, mem, vf2pf_to * HZ); + if (!ret) + ret = hdr->status; + if (!ret) + reg = msg->reg; + kfree(mem); + return reg; +} + +/* Only called for VFs */ +u64 hfi2_pf0_rctxt_ctrl_op(struct hfi2_devdata *dd, u16 ctxt, unsigned int op) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_rcctrl_msg *msg; + void *mem; + u64 reg = ~(u64)0; /* error */ + int ret; + + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + return hfi2_rctxt_ctrl_op(pdd, ctxt, op); + } + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct vf2pf_rcctrl_msg *)hdr; + msg->hdr.op = VF2PF_RCCTRL_OP; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + msg->ctxt = ctxt; + msg->op = op; + ret = vf2pf_send_recv(dd, 0, mem, vf2pf_to * HZ); + if (!ret) + ret = hdr->status; + if (!ret) + reg = msg->reg; + kfree(mem); + return reg; +} + +void hfi2_vf2pf_tid_config(struct hfi2_devdata *dd, int pidx, u16 ctxt, + u32 eager_base, u16 alloced, u32 expected_base, + u32 expected_count) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_tidcfg_msg *msg; + void *mem; + + if (!dd->is_vf) + return; /*should never happen */ + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + pdd->params->set_port_tid_config(pdd, pidx, ctxt, eager_base, + alloced, expected_base, + expected_count); + return; + } + mem = msg_alloc(dd, &hdr); + if (!mem) { + dd_dev_err( + dd, + "Failed to allocate vf2pf message buffer for tid_config\n"); + return; + } + msg = (struct vf2pf_tidcfg_msg *)hdr; + msg->hdr.op = VF2PF_TIDCFG_OP; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + msg->pidx = pidx; + msg->ctxt = ctxt; + msg->alloced = alloced; + msg->egr_base = eager_base; + msg->exp_base = expected_base; + msg->exp_cnt = expected_count; + vf2pf_send_recv(dd, 0, mem, vf2pf_to * HZ); + kfree(mem); +} + +int hfi2_vf2pf_init_rxe_rsm(struct hfi2_devdata *dd) +{ + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + return hfi2_init_rxe_rsm(pdd, &dd->rsrcs); + } + return do_asgnrs_msg(dd, VF2PF_RXERSM_OP, &dd->rsrcs); +} + +u16 hfi2_vf2pf_get_qp_map(struct hfi2_devdata *dd, int pidx, u16 idx) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_qpmap_msg *msg; + void *mem; + u16 res = 0; /* guaranteed error */ + int ret; + + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + return hfi2_get_qp_map(pdd->pport + pidx, idx); + } + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct vf2pf_qpmap_msg *)hdr; + msg->hdr.op = VF2PF_QPMAP_OP; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + msg->pidx = pidx; + msg->idx = idx; + ret = vf2pf_send_recv(dd, 0, mem, vf2pf_to * HZ); + if (!ret) + ret = hdr->status; + if (!ret) + res = msg->res; + kfree(mem); + return res; +} + +/* + * called on PF0 to distribute port_info to all VFs. + */ +int hfi2_pf2vf_push_portinfo(struct hfi2_pportdata *ppd, struct opa_smp *smp, + struct opa_port_info *pi, int si_mask) +{ + struct hfi2_devdata *dd = ppd->dd, *vdd; + struct pci_dev *pdev, *vpdev; + struct vf2pf_hdr *hdr; + struct pf0_pushpi_msg *msg; + void *mem; + int id; + int ret; + + if (dd->is_vf) + return -EINVAL; + + if (si_mask == VF2PF_SI_ALL) + si_mask = dd->rsrcs.sync_done; + if (!si_mask) + return 0; + + pdev = dd->pcidev; + /* + * However, it is possible that some VFs may be local and some in VMs, + * so might need to have each VF differently. At least, though, we can + * only allocate message buffer once. + */ + /* + * 'pi' is always opa_get_smp_data(smp) so we only + * need to send 'smp' (the whole MAD). + */ + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct pf0_pushpi_msg *)hdr; + msg->hdr.op = PF0_PUSH_PI; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + msg->pidx = ppd->hw_pidx; + memcpy(&msg->smp, smp, sizeof(*smp)); + for (id = 0; id < dd->rsrcs.num_vfs; ++id) { + if (!(si_mask & (1 << (id + 1)))) + continue; + /* + * pci/iov.c uses pci_iov_virtfn_bus(pdev, id) but we don't have that, + * will pdev->bus->number work? + */ + vpdev = pci_get_domain_bus_and_slot( + pci_domain_nr(pdev->bus), pdev->bus->number, + pci_iov_virtfn_devfn(pdev, id)); + if (!vpdev) + continue; /* error or just skip? */ + vdd = pci_get_drvdata(vpdev); + if (IS_LOCAL_VDD(vdd)) { /* must not be in VM... */ + ret = hfi2_update_from_opa_portinfo( + &vdd->pport[ppd->hw_pidx], smp, pi); + } else { + ret = vf2pf_send(dd, id + 1, mem); + if (ret) + dd_dev_warn( + dd, + "Failed to push portinfo to %d (%d)\n", + id + 1, ret); + } + if (ret) + break; + } + kfree(mem); + return ret; +} + +/* + * called on PF0 to distribute sc2vlt to all VFs. + */ +int hfi2_pf2vf_push_sc2vlt(struct hfi2_pportdata *ppd, int si_mask) +{ + struct hfi2_devdata *dd = ppd->dd, *vdd; + struct pci_dev *pdev, *vpdev; + struct vf2pf_hdr *hdr; + struct pf0_pushvlt_msg *msg; + void *mem; + int id; + int ret = 0; + + if (dd->is_vf) + return -EINVAL; + + if (si_mask == VF2PF_SI_ALL) + si_mask = dd->rsrcs.sync_done; + if (!si_mask) + return 0; + + pdev = dd->pcidev; + /* + * However, it is possible that some VFs may be local and some in VMs, + * so might need to have each VF differently. At least, though, we can + * only allocate message buffer once. + */ + /* + * 'pi' is always opa_get_smp_data(smp) so we only + * need to send 'smp' (the whole MAD). + */ + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct pf0_pushvlt_msg *)hdr; + msg->hdr.op = PF0_PUSH_VLT; + msg->hdr.len = sizeof(*msg) - sizeof(*hdr); + msg->pidx = ppd->hw_pidx; + memcpy(msg->sc2vl, ppd->sc2vl, sizeof(msg->sc2vl)); + for (id = 0; id < dd->rsrcs.num_vfs; ++id) { + if (!(si_mask & (1 << (id + 1)))) + continue; + /* + * pci/iov.c uses pci_iov_virtfn_bus(pdev, id) but we don't have that, + * will pdev->bus->number work? + */ + vpdev = pci_get_domain_bus_and_slot( + pci_domain_nr(pdev->bus), pdev->bus->number, + pci_iov_virtfn_devfn(pdev, id)); + if (!vpdev) + continue; /* error or just skip? */ + vdd = pci_get_drvdata(vpdev); + if (IS_LOCAL_VDD(vdd)) { /* must not be in VM... */ + hfi2_update_sc2vlt(&vdd->pport[ppd->hw_pidx], + ppd->sc2vl, false); + } else { + ret = vf2pf_send(dd, id + 1, mem); + if (ret) + dd_dev_warn( + dd, + "Failed to push sc2vlt to %d (%d)\n", + id + 1, ret); + } + if (ret) + break; + } + kfree(mem); + return ret; +} + +int hfi2_vf2pf_send_only_mad(struct hfi2_devdata *dd, u8 sb, const void *mad, + int len) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_mad *msg; + void *mem; + int ret; + + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + return hfi2_cport_send_only_mad(pdd, sb, mad, len); + } + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct vf2pf_mad *)hdr; + msg->hdr.op = VF2PF_MAD_SND; + msg->hdr.len = len + VF2PF_MAD_OVERHEAD; + msg->sb = sb; + memcpy(&msg->mad, mad, len); + ret = vf2pf_send(dd, 0, mem); + if (!ret) + ret = hdr->status; + kfree(mem); + return ret; +} + +int hfi2_vf2pf_send_recv_mad(struct hfi2_devdata *dd, u8 sb, const void *mad, + int len, void *omad, size_t *omad_len, long to) +{ + struct vf2pf_hdr *hdr; + struct vf2pf_mad *msg; + void *mem; + int ret; + + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + return hfi2_cport_send_recv_mad(pdd, sb, mad, len, omad, omad_len); + } + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + msg = (struct vf2pf_mad *)hdr; + msg->hdr.op = VF2PF_MAD_SNDRCV; + msg->hdr.len = len + VF2PF_MAD_OVERHEAD; + msg->sb = sb; + memcpy(&msg->mad, mad, len); + ret = vf2pf_send_recv(dd, 0, mem, -to); /* -to: signal use semaphore */ + if (!ret) + ret = hdr->status; + if (!ret) { + int olen = msg->hdr.len - VF2PF_MAD_OVERHEAD; + + if (olen > *omad_len) { + dd_dev_warn( + dd, + "VF2PF MAD resp length 0x%x > 0x%lx, truncating\n", + olen, *omad_len); + olen = *omad_len; + } + memcpy(omad, &msg->mad, olen); + } + kfree(mem); + return ret; +} + +static void vf2pf_syncup(struct hfi2_devdata *dd, int si) +{ + atomic_or(1 << si, &dd->rsrcs.sync_pending); + queue_work(dd->hfi2_wq, &dd->sync_vf_work); +} + +static void vf2pf_sync_fn(struct work_struct *work) +{ + int ret; + + struct hfi2_devdata *dd = + container_of(work, struct hfi2_devdata, sync_vf_work); + int sync_pending = atomic_fetch_and(0, &dd->rsrcs.sync_pending); + + if (!sync_pending) + return; + + dd_dev_info(dd, "syncing VFs %02x\n", sync_pending); + dd->rsrcs.sync_done |= sync_pending; + ret = hfi2_sriov_sync_ports(dd, sync_pending); + if (ret) + dd_dev_err(dd, "Failed to sync ports to %02x (%d)\n", + sync_pending, ret); +} + +/* + * received responses handled elsewhere. + * 'buf' (and 'hdr') are allocated memory. + * must be safe to destroy 'buf' on return. + */ +void hfi2_vf2pf_rcv_msg(struct hfi2_devdata *dd, struct vf2pf_hdr *hdr, void *buf) +{ + int ret = 0; + + /* hdr == vf2pf_dev->get_msg(dd, buf) */ + switch (hdr->op) { + case VF2PF_OP_PING: { + struct vf2pf_ping_msg *ping = (struct vf2pf_ping_msg *)hdr; + + dd_dev_info(dd, "vf2pf ping-pong with %u \"%.*s\"\n", hdr->si, + hdr->len, ping->data); + if (hdr->len >= sizeof(ping->data)) + hdr->len = sizeof(ping->data) - 1; + ping->data[hdr->len++] = '!'; + break; + } + + /* only received on PF0 */ + case VF2PF_GET_CFG: { + struct vf2pf_getcfg_msg *msg = (struct vf2pf_getcfg_msg *)hdr; + + ret = hfi2_sriov_get_config(dd, &msg->rsrcs, msg->si); + /* copy these even if error */ + msg->base_guid = dd->base_guid; + msg->revision = dd->revision; + msg->hfi2_id = dd->hfi2_id; + msg->icode = dd->icode; + msg->irev = dd->irev; + msg->cport_ver = dd->cport_ver; + break; + } + case VF2PF_ASGN_RES: { + struct vf2pf_asgnrs_msg *msg = (struct vf2pf_asgnrs_msg *)hdr; + + ret = hfi2_sriov_assign_rsrcs(dd, &msg->rsrcs); + break; + } + case VF2PF_FREE_RES: { + struct vf2pf_asgnrs_msg *msg = (struct vf2pf_asgnrs_msg *)hdr; + + hfi2_sriov_free_rsrcs(dd, &msg->rsrcs); + break; + } + case VF2PF_PREG_OP: { + struct vf2pf_pregop_msg *msg = (struct vf2pf_pregop_msg *)hdr; + + ret = hfi2_priv_reg_op(dd, msg->pidx, msg->ctxt, msg->type, msg->op, + msg->arg); + break; + } + case VF2PF_RCSR_OP: { + struct vf2pf_readcsr_msg *msg = (struct vf2pf_readcsr_msg *)hdr; + + msg->reg = hfi2_read_csr(dd, msg->off); + break; + } + case VF2PF_RCCTRL_OP: { + struct vf2pf_rcctrl_msg *msg = (struct vf2pf_rcctrl_msg *)hdr; + + ret = hfi2_rctxt_ctrl_op(dd, msg->ctxt, msg->op); + break; + } + case VF2PF_TIDCFG_OP: { + struct vf2pf_tidcfg_msg *msg = (struct vf2pf_tidcfg_msg *)hdr; + + dd->params->set_port_tid_config(dd, msg->pidx, msg->ctxt, + msg->egr_base, msg->alloced, + msg->exp_base, msg->exp_cnt); + break; + } + case VF2PF_RXERSM_OP: { + struct vf2pf_asgnrs_msg *msg = (struct vf2pf_asgnrs_msg *)hdr; + + ret = hfi2_init_rxe_rsm(dd, &msg->rsrcs); + break; + } + case VF2PF_QPMAP_OP: { + struct vf2pf_qpmap_msg *msg = (struct vf2pf_qpmap_msg *)hdr; + + msg->res = hfi2_get_qp_map(dd->pport + msg->pidx, msg->idx); + break; + } + case VF2PF_STOP: { + if (vf2pf_dev->deinit) + vf2pf_dev->deinit(dd, hdr->si); + return; /* no response */ + } + case VF2PF_MAD_SND: { + struct vf2pf_mad *msg = (struct vf2pf_mad *)hdr; + + ret = hfi2_cport_send_only_mad(dd, msg->sb, &msg->mad, + msg->hdr.len - VF2PF_MAD_OVERHEAD); + if (ret) + dd_dev_err(dd, "Failed to send MAD to CPORT (%d)\n", + ret); + return; /* no response */ + } + case VF2PF_MAD_SNDRCV: { + struct vf2pf_mad *msg = (struct vf2pf_mad *)hdr; + size_t olen = sizeof(msg->mad); + + ret = hfi2_cport_send_recv_mad(dd, msg->sb, &msg->mad, + msg->hdr.len - VF2PF_MAD_OVERHEAD, + &msg->mad, &olen); + if (!ret) + msg->hdr.len = olen; + break; + } + case VF2PF_READY: { + vf2pf_syncup(dd, hdr->si); + return; /* no response */ + } + + /* only received on VFs */ + case PF0_PUSH_PI: { + struct pf0_pushpi_msg *msg = (struct pf0_pushpi_msg *)hdr; + struct opa_port_info *pi = + (struct opa_port_info *)opa_get_smp_data(&msg->smp); + + ret = hfi2_update_from_opa_portinfo(dd->pport + msg->pidx, &msg->smp, + pi); + if (ret) + dd_dev_err(dd, + "Failed to process port_info update (%d)\n", + ret); + return; /* no response */ + } + case PF0_PUSH_VLT: { + struct pf0_pushvlt_msg *msg = (struct pf0_pushvlt_msg *)hdr; + + hfi2_update_sc2vlt(dd->pport + msg->pidx, msg->sc2vl, false); + return; /* no response */ + } + default: + dd_dev_err(dd, "Unknown vf2pf msg op %u from %u\n", hdr->op, + hdr->si); + return; + } + + /* reaching here means response is to be sent */ + hdr->op |= VF2PF_OP_RESP; + hdr->status = ret; + vf2pf_send(dd, hdr->si, buf); +} + +/* + * 'buf' may point to h/w recv buffer. + * called from intr context: must expedite handling. + * hdr->op has VF2PF_OP_RESP set, in order to reach here. + */ +void hfi2_vf2pf_rsp_msg(struct hfi2_devdata *dd, void *buf) +{ + struct vf2pf_hdr *hdr = buf; + struct vf2pf_prefix *wpfx; /* msg object waiting for response */ + struct vf2pf_hdr *whdr; /* hdr waiting for response */ + void *wbuf; + + wpfx = vf2pf_dev->get_tid(dd, hdr->tid); + if (!wpfx) { + dd_dev_err(dd, "vf2pf response op %x has no waiter\n", hdr->op); + return; + } + whdr = vf2pf_dev->get_msg(dd, wpfx); + wbuf = whdr; + /* + * need to avoid race by setting VF2PF_OP_RESP in whdr->op last (after barrier). + * this requires that VF2PF_OP_RESP is cleared in 'op' before the memcpy, + * then set after in the destination. + */ + hdr->op &= ~VF2PF_OP_RESP; + memcpy(wbuf, buf, hdr->len + sizeof(*hdr)); + smp_wmb(); /* needed? */ + whdr->op |= VF2PF_OP_RESP; /* to trigger wakeup condition */ + if (wpfx->type == VF2PF_PFX_TYPE_WAIT) + wake_up(&wpfx->wait); + else + up(&wpfx->sema); +} + +/* + * Return number of special contexts needed by implementation. + * Should be either 0 or JKR_C_CCE_NUM_VFS + 1. + */ +int hfi2_vf2pf_num_ctxts(struct hfi2_devdata *dd) +{ + if (dd->params->chip_type == CHIP_WFR || !dd->is_sriov) + return 0; + return vf2pf_dev->num_ctxts; +} + +int hfi2_vf2pf_num_irq(struct hfi2_devdata *dd) +{ + if (dd->params->chip_type == CHIP_WFR || !dd->is_sriov) + return 0; + return vf2pf_dev->num_irq; +} + +/* returns 0 on error (invalid VF SI) */ +int hfi2_vf2pf_probe_si(struct hfi2_devdata *dd) +{ + if (vf2pf_dev->probe_si) + return vf2pf_dev->probe_si(dd); + return 0; +} + +static ssize_t vf2pf_ping_store(struct device *device, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct hfi2_ibdev *dev = + rdma_device_to_drv_device(device, struct hfi2_ibdev, rdi.ibdev); + struct hfi2_devdata *dd = dd_from_dev(dev); + void *mem; + struct vf2pf_hdr *hdr; + struct vf2pf_ping_msg *ping; + size_t len = count; + int ret; + + if (count > sizeof(ping->data)) + return -EINVAL; + + /* trim one newline if present */ + if (buf[len - 1] == '\n') + --len; + + mem = msg_alloc(dd, &hdr); + if (!mem) + return -ENOMEM; + + ping = (struct vf2pf_ping_msg *)hdr; + ping->hdr.op = VF2PF_OP_PING; + ping->hdr.len = len; + memcpy(ping->data, buf, len); + + dd_dev_info(dd, "vf2pf ping 0 \"%.*s\"\n", ping->hdr.len, buf); + ret = vf2pf_send_recv(dd, 0, mem, vf2pf_to * HZ); + if (ret) + dd_dev_warn(dd, "vf2pf ping send failed (%d)\n", ret); + else + dd_dev_info(dd, "vf2pf ping resp from %u \"%.*s\"\n", + ping->hdr.si, ping->hdr.len, (char *)ping->data); + kfree(mem); + return count; +} + +static DEVICE_ATTR_WO(vf2pf_ping); + +static ssize_t vf2pf_sync_store(struct device *device, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct hfi2_ibdev *dev = + rdma_device_to_drv_device(device, struct hfi2_ibdev, rdi.ibdev); + struct hfi2_devdata *dd = dd_from_dev(dev); + unsigned long sync_mask; + int ret; + + /* allow 0 mask and trigger work func anyway */ + ret = kstrtoul(buf, 0, &sync_mask); + if (ret || (sync_mask & ~0b011111110)) + return -EINVAL; + + atomic_or(sync_mask, &dd->rsrcs.sync_pending); + queue_work(dd->hfi2_wq, &dd->sync_vf_work); + + return count; +} + +static DEVICE_ATTR_WO(vf2pf_sync); + +void hfi2_vf2pf_set_si_enables(struct hfi2_devdata *dd, int si, u64 *csrs, + void (*si_enables)(struct hfi2_devdata *dd, u64 *csrs, + u32 start, u32 end)) +{ + if (!vf2pf_dev->set_si_enables) + return; + vf2pf_dev->set_si_enables(dd, si, csrs, si_enables); +} + +void hfi2_vf2pf_ready(struct hfi2_devdata *dd) +{ + struct vf2pf_hdr *hdr; + void *mem; + int ret; + + if (!dd->is_vf) + return; + + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = pci_get_drvdata(dd->pcidev->physfn); + + vf2pf_syncup(pdd, dd->rsrcs.si_idx); + return; + } + mem = msg_alloc(dd, &hdr); + if (!mem) { + dd_dev_err(dd, "Failed to signal ready to PF0 (msg_alloc)\n"); + return; + } + hdr->op = VF2PF_READY; + hdr->len = 0; + ret = vf2pf_send(dd, 0, mem); + kfree(mem); + if (ret) + dd_dev_err(dd, "Failed to signal ready to PF0 (%d)\n", ret); +} + +void hfi2_vf2pf_init_sysfs(struct hfi2_devdata *dd, struct device *class_dev) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + int ret; + + if (!lbd) + return; + + if (dd->is_vf) { + ret = sysfs_create_file(&class_dev->kobj, + &dev_attr_vf2pf_ping.attr); + if (ret) + dd_dev_warn(dd, "failed to create sysfs attr %s (%d)\n", + dev_attr_vf2pf_ping.attr.name, ret); + } else { + ret = sysfs_create_file(&class_dev->kobj, + &dev_attr_vf2pf_sync.attr); + if (ret) + dd_dev_warn(dd, "failed to create sysfs attr %s (%d)\n", + dev_attr_vf2pf_sync.attr.name, ret); + } + if (vf2pf_dev->init_sysfs) + vf2pf_dev->init_sysfs(dd, class_dev); +} + +int hfi2_vf2pf_init_irq(struct hfi2_devdata *dd) +{ + if (!vf2pf_dev->init_irq) + return 0; + return vf2pf_dev->init_irq(dd); +} + +void hfi2_vf2pf_deinit_irq(struct hfi2_devdata *dd) +{ + if (!vf2pf_dev->deinit_irq) + return; + vf2pf_dev->deinit_irq(dd); +} + +/* + * This is called on PF0 only, just before creation of VFs. + * + * This may be called multiple times throughout the life of the PF0 + * driver, if VFs are destroyed and recreated. + */ +int hfi2_vf2pf_prep(struct hfi2_devdata *dd) +{ + if (!vf2pf_dev->init) + return 0; + return vf2pf_dev->init(dd, VF2PF_INIT_ALL); +} + +/* + * This is called early in the initialization. + * It must not depend on any SRIOV configuration being setup, + * but may call into the sriov module to decide if SRIOV is allowed. + */ +int hfi2_vf2pf_early_init(struct hfi2_devdata *dd) +{ + if (dd->params->chip_type == CHIP_WFR || !dd->is_sriov) + return 0; + + if (!dd->is_vf) { + INIT_WORK(&dd->sync_vf_work, vf2pf_sync_fn); + atomic_set(&dd->rsrcs.sync_pending, 0); + dd->rsrcs.sync_done = 0; + } + +#ifdef HFI_VF2PF_LOOPBACK +#ifndef HFI_VF2PF_LOOPBACK_CONFIG + if (vf2pf_lb) +#endif + vf2pf_dev = hfi2_get_lb_devops(); +#endif + /* this may require BARs, must have been mapped by now */ + return hfi2_sriov_set_si(dd); +} + +/* + * This does the actual vf2pf implementation init, + * which may need to be done later. + */ +int hfi2_vf2pf_init(struct hfi2_devdata *dd) +{ + if (dd->params->chip_type == CHIP_WFR || !dd->is_sriov) + return 0; + + if (!vf2pf_dev->init) + return 0; + return vf2pf_dev->init(dd, dd->rsrcs.si_idx); +} + +/* + * On VFs, this only sends a notification to PF0. + * On PF0, this does a full de-initialization. + */ +void hfi2_vf2pf_deinit(struct hfi2_devdata *dd) +{ + struct vf2pf_hdr *hdr; + void *mem; + int ret; + + if (dd->is_vf) { + if (IS_LOCAL_VF( + dd)) { /* VF and PF0 are using the same driver/OS instance */ + struct hfi2_devdata *pdd = + pci_get_drvdata(dd->pcidev->physfn); + + if (vf2pf_dev->deinit) + vf2pf_dev->deinit(pdd, dd->rsrcs.si_idx); + goto out; + } + mem = msg_alloc(dd, &hdr); + if (!mem) { + dd_dev_err(dd, "Failed to notify PF0 (msg_alloc)\n"); + return; + } + hdr->op = VF2PF_STOP; + hdr->len = 0; + ret = vf2pf_send(dd, 0, mem); + kfree(mem); + if (ret) + dd_dev_err(dd, "Failed to notify PF0 (%d)\n", ret); + } +out: + if (vf2pf_dev->deinit) + vf2pf_dev->deinit(dd, dd->rsrcs.si_idx); +} diff --git a/drivers/infiniband/hw/hfi2/vf2pf_lb.c b/drivers/infiniband/hw/hfi2/vf2pf_lb.c new file mode 100644 index 000000000000..6155cc3eb9fd --- /dev/null +++ b/drivers/infiniband/hw/hfi2/vf2pf_lb.c @@ -0,0 +1,964 @@ +// SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause +/* + * Copyright(c) 2025-2026 Cornelis Networks, Inc. + * + * SRIOV support for VFs making requests to PF0 over hfi2_loopback. + */ + +#include "hfi2.h" +#include "chip_jkr.h" +#include "chip_gen.h" +#include "exp_rcv.h" +#include "sriov.h" +#include "vf2pf_int.h" +#include "vf2pf_lb.h" + +static uint vf2pf_lb_port; + +#undef VF2PF_LB_DEBUG +#define LB_RCV_CANT_SLEEP + +#define LB_IN_INTR 0 +#define LB_IN_THREAD 1 +#define LB_IN_POLL 2 +#define LB_IN_SHUTDOWN 3 + +#define LB_RHQ_ENT_SIZE 32 /* same as default for hfi2_hdrq_entsize */ +#define LB_MAX_RCV_MSG PAGE_SIZE +#define LB_DEFAULT_RCVHDRSIZE 2 /* get split-point right */ + +static void lb_deinit(struct hfi2_devdata *dd, u8 si); +static struct vf2pf_hdr *lb_get_msg(struct hfi2_devdata *dd, void *buf); +static void *lb_msg_alloc(struct hfi2_devdata *dd, struct vf2pf_hdr **msg); + +static struct vf2pf_devops vf2pf_lb_dev; + +/* + * Get vf2pf_lb context resources for specific SI. + * These should be valid early, as soon as CSRs are accessible. + * Only called by PF0. + */ +static void lb_set_si_ctxtrsrcs(struct hfi2_devdata *dd, int si, + struct hfi2_ctxtrsrcs *lbr) +{ + int nctxt = vf2pf_lb_dev.num_ctxts; + + lbr->first_rcv_context = chip_rcv_contexts(dd) - nctxt + si; + lbr->last_rcv_context = lbr->first_rcv_context + 1; + lbr->first_send_context = chip_send_contexts(dd) - nctxt + si; + lbr->last_send_context = lbr->first_send_context + 1; + lbr->first_rcvarray_entry = chip_rcv_array_count(dd) - + HFI_MIN_PF0_RCVARY(nctxt) + + HFI_MIN_PF0_RCVARY(si); + lbr->last_rcvarray_entry = + lbr->first_rcvarray_entry + HFI_MIN_PF0_RCVARY(1); + lbr->first_pio_block = chip_pio_mem_size(dd) / PIO_BLOCK_SIZE - + HFI_MIN_PF0_PIO(nctxt) + HFI_MIN_PF0_PIO(si); + lbr->last_pio_block = lbr->first_pio_block + HFI_MIN_PF0_PIO(1); +} + +static void lb_rcv_msg(struct work_struct *work) +{ + struct vf2pf_lb_msg *msg = + container_of(work, struct vf2pf_lb_msg, pfx.work); + struct hfi2_devdata *dd = msg->pfx.dd; + + hfi2_vf2pf_rcv_msg(dd, &msg->hdr, msg); + kfree(msg); +} + +/* + * 'buf' is struct vf2pf_hdr plus payload, in h/w recv buffer (eager buf). + * copy out and spawn to kworker thread. + */ +static void lb_queue_rcv_work(struct hfi2_devdata *dd, void *buf) +{ + struct vf2pf_hdr *hdr = buf; + struct vf2pf_lb_msg *msg; + void *out; + + out = lb_msg_alloc(dd, NULL); + if (!out) { + dd_dev_err(dd, "vf2pf no memory for rcv work\n"); + return; + } + msg = out; + INIT_WORK(&msg->pfx.work, lb_rcv_msg); + msg->pfx.dd = dd; + msg->pfx.len = hdr->len + sizeof(*hdr); + memcpy(out + offsetof(struct vf2pf_lb_msg, hdr), buf, msg->pfx.len); + queue_work(dd->hfi2_wq, &msg->pfx.work); +} + +static inline u32 rhf_egr_index(u64 rhf) +{ + /* NOTE: RHF.EgrIndex is 14 bits but RcvEgrIndexHead has 16 bits */ + return (rhf >> 16) & 0x3fff; +} + +/* returns 0 if queue was emptied */ +static int lb_do_rcv(struct hfi2_devdata *dd, int thread) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + u16 ctxt = lbd->c.first_rcv_context; + u32 head, tail; + u32 rhqoff, etail; + u32 rhq_max; + u64 rhf, rhe; + bool bad; + void *buf; + u32 len; + + if (!lbd || + ((lbd->flags & VF2PF_LB_FL_SHUTDOWN) && thread != LB_IN_SHUTDOWN)) + return RCV_PKT_DONE; + + rhq_max = lbd->b.rhq_cnt * lbd->b.rhq_ent_size; + /* + * process receive: + * + * (hdrq)TAIL is set by HFI to the next hdr ent to be used on next pkt recvd. + * (hdrq)HEAD is used by driver to pull packet hdrs off queue (DMA memory). + * units are DW, incr by RcvHdrEntSize. + * + * RHEQ entry is same index as RHQ (diff element size). + * + * RHF contains index/offset for payload in eager buffer. + */ + head = (u32)read_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg); + tail = (u32)read_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_tail_reg); + rhqoff = head; /* always multiple of rcvhdrqentsize, units dwords */ + while (rhqoff != tail) { + rhf = rhf_to_cpu((__le32 *)lbd->b.rhq.va + rhqoff + + LB_RHQ_ENT_SIZE - sizeof(u64) / sizeof(u32)); + rhe = *((u64 *)lbd->b.rheq.va + rhqoff / LB_RHQ_ENT_SIZE); + etail = rhf_egr_index(rhf); /* RHF.EgrIndex */ +#ifdef VF2PF_LB_DEBUG + dd_dev_info(dd, + "%s: head=%04x tail=%04x rhf=%016llx rhe=%016llx\n", + __func__, rhqoff, tail, rhf, rhe); +#endif + len = rhf_pkt_len(rhf); /* in bytes */ + bad = true; + if (rhe & RHF_ERROR_SMASK) + goto drop; + if (rhf_rcv_type(rhf) != RHF_RCV_TYPE_EAGER) + goto drop; + if (rhf_use_egr_bfr(rhf)) + buf = lbd->b.egr.va + + rhf_egr_index(rhf) * lbd->b.egr_buf_size + + rhf_egr_buf_offset(rhf) * RCV_BUF_BLOCK_SIZE; + else + buf = lbd->b.rhq.va + + (rhqoff + rhf_hdrq_offset(rhf)) * sizeof(u32) + + sizeof(struct vf2pf_lb_hdr); +#ifdef VF2PF_LB_DEBUG + dd_dev_info(dd, "LB_PKT @ %p idx=%u off=%u\n", buf, + rhf_egr_index(rhf), rhf_egr_buf_offset(rhf)); + print_hex_dump(KERN_INFO, "LB_PKT ", DUMP_PREFIX_OFFSET, 16, 1, + buf, len - sizeof(struct vf2pf_lb_hdr), false); +#endif + + /* divert responses now (do not use WQ) */ + if (((struct vf2pf_hdr *)buf)->op & VF2PF_OP_RESP) { + hfi2_vf2pf_rsp_msg(dd, buf); + goto next; + } + lb_queue_rcv_work(dd, buf); +next: + bad = false; +drop: + if (bad) + dd_dev_err( + dd, + "Recv error ctxt %u rhq=%04x rhf=%016llx rhe=%016llx\n", + ctxt, rhqoff, rhf, rhe); + rhqoff += LB_RHQ_ENT_SIZE; + if (rhqoff >= rhq_max) + rhqoff = 0; +#ifdef VF2PF_LB_DEBUG + dd_dev_info(dd, "%s: hfi2_update_usrhead_ctxt(%u, %04x, 1, %04x)\n", + __func__, ctxt, rhqoff, etail); +#endif + hfi2_update_usrhead_ctxt(dd, ctxt, rhqoff, 1, 1, etail); + } + return RCV_PKT_DONE; +} + +static inline int lb_rsp_wait(struct vf2pf_prefix *pfx, struct vf2pf_hdr *hdr, + long timeout) +{ + int ret; + +#ifdef LB_RCV_CANT_SLEEP + unsigned long expire; + + if (timeout > 0 && timeout != MAX_SCHEDULE_TIMEOUT) + expire = timeout + jiffies; + else + expire = 0; + ret = 0; + while (!(hdr->op & VF2PF_OP_RESP)) { + udelay(100); + if (expire && time_after_eq(jiffies, expire)) + return -ETIME; + } +#else + ret = wait_event_timeout(pfx->wait, (hdr->op & VF2PF_OP_RESP), timeout); + ret = ret ? 0 : -ETIME; /* convert residual time to error */ +#endif + + return ret; +} + +static int lb_rcv_wait(struct hfi2_devdata *dd, void *buf, long timeout) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + struct vf2pf_prefix *pfx = buf; + struct vf2pf_hdr *hdr; + int ret = 0; + + if (!lbd) + return -EINVAL; + + hdr = lb_get_msg(dd, buf); + if (lbd->rcv_irq) { + ret = lb_rsp_wait(pfx, hdr, timeout); + } else { + u32 head, tail; + unsigned long expire; + u16 ctxt = lbd->c.first_rcv_context; + + /* interrupts not setup yet, poll for recv. */ + if (timeout > 0 && timeout != MAX_SCHEDULE_TIMEOUT) + expire = timeout + jiffies; + else + expire = 0; + while (!(hdr->op & VF2PF_OP_RESP)) { + spin_lock(&lbd->rcv_lock); + head = (u32)read_uctxt_csr( + dd, ctxt, dd->params->rcv_hdr_head_reg); + tail = (u32)read_uctxt_csr( + dd, ctxt, dd->params->rcv_hdr_tail_reg); + if (head != tail) { + lb_do_rcv(dd, LB_IN_POLL); + spin_unlock(&lbd->rcv_lock); + } else { + spin_unlock(&lbd->rcv_lock); + udelay(100); + if (expire && time_after_eq(jiffies, expire)) + return -ETIME; + } + } + } + return ret; +} + +/* + * This is like __hfi2_rcd_eoi_intr() except not dependent on hfi2_ctxtdata. + */ +static void __hfi2_rctxt_eoi_intr(struct hfi2_devdata *dd, u16 ctxt) +{ + u32 src = dd->params->is_rcvavail_start + ctxt; + u32 off = sizeof(u64) * (src / 64); + u64 bit = 1ull << (src % 64); + u32 head, tail; + + hfi2_write_csr(dd, dd->params->cce_int_clear_reg + off, bit); + + /* these also force the previous write */ + head = (u32)read_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_head_reg); + tail = (u32)read_uctxt_csr(dd, ctxt, dd->params->rcv_hdr_tail_reg); + if (head != tail) + hfi2_write_csr(dd, dd->params->cce_int_force_reg + off, bit); +} + +static irqreturn_t lb_rcv_intr(int irq, void *arg) +{ + struct hfi2_devdata *dd = arg; + struct vf2pf_lbdata *lbd = dd->vf2pf; + + if (!lbd) + return IRQ_HANDLED; + + this_cpu_inc(*dd->int_counter); + +#ifdef VF2PF_LB_DEBUG + dd_dev_info(dd, "Receive vf2pf interrupt on %u\n", + lbd->c.first_rcv_context); +#endif + if (lb_do_rcv(dd, LB_IN_INTR) == RCV_PKT_LIMIT) + return IRQ_WAKE_THREAD; + + __hfi2_rctxt_eoi_intr(dd, lbd->c.first_rcv_context); + return IRQ_HANDLED; +} + +static irqreturn_t lb_rcv_thrd(int irq, void *arg) +{ + struct hfi2_devdata *dd = arg; + struct vf2pf_lbdata *lbd = dd->vf2pf; + + if (!lbd) + return IRQ_HANDLED; +#ifdef VF2PF_LB_DEBUG + dd_dev_info(dd, "Receive vf2pf thread on %u\n", + lbd->c.first_rcv_context); +#endif + lb_do_rcv(dd, LB_IN_THREAD); + + __hfi2_rctxt_eoi_intr(dd, lbd->c.first_rcv_context); + return IRQ_HANDLED; +} + +#define lb_tid_limit XA_LIMIT(0, 255) /* must fit in u16 */ + +static u16 lb_set_tid(struct hfi2_devdata *dd, void *tok) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + u32 idx; + int ret; + + if (!lbd) + return ~(u16)0; + ret = xa_alloc_cyclic(&lbd->tid_xa, &idx, tok, lb_tid_limit, + &lbd->tid_next, GFP_KERNEL); + if (ret < 0) + return ~(u16)0; + return (u16)idx; +} + +/* destructive lookup... */ +static void *lb_get_tid(struct hfi2_devdata *dd, u16 tid) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + + if (!lbd) + return NULL; + return xa_erase(&lbd->tid_xa, (u32)tid); +} + +static int lb_init_irq(struct hfi2_devdata *dd) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + char name[MAX_NAME_SIZE]; + int ret; + u16 ctxt; + + if (!lbd) + return 0; + ctxt = lbd->c.first_rcv_context; + + snprintf(name, sizeof(name), DRIVER_NAME "_%dsi%d", dd->rsrcs.pfunit, + dd->rsrcs.si_idx); + /* + * Use IRQ_GENERAL even though this is IRQ_RCVCTXT, to avoid + * doing affinity work that is not possible for this ctxt + * (no struct hfi2_ctxtdata *rcd exists). + */ + ret = hfi2_msix_request_irq_remap(dd, ctxt, IRQ_GENERAL, + dd->params->is_rcvavail_start + ctxt, + lb_rcv_intr, lb_rcv_thrd, dd, name); + if (ret < 0) + return ret; + if (!ret) { + /* must not take intr 0 */ + hfi2_msix_free_irq(dd, (u8)ret); + return -ENOSPC; + } + lbd->rcv_irq = ret; + hfi2_set_intr_bits(dd, dd->params->is_rcvavail_start + ctxt, + dd->params->is_rcvavail_start + ctxt, true); + return 0; +} + +static void lb_deinit_irq(struct hfi2_devdata *dd) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + u16 ctxt; + u8 irq; + + if (!lbd) + return; + irq = lbd->rcv_irq; + if (lbd->rcv_irq <= 0) + return; + lbd->rcv_irq = 0; /* trigger polling */ + + ctxt = lbd->c.first_rcv_context; + /* tear-down our interrupt */ + hfi2_set_intr_bits(dd, dd->params->is_rcvavail_start + ctxt, + dd->params->is_rcvavail_start + ctxt, false); + hfi2_msix_free_irq(dd, irq); +#ifdef VF2PF_LB_DEBUG + dd_dev_info(dd, "Calling lb_do_rcv() in shutdown\n"); +#endif + lb_do_rcv(dd, LB_IN_SHUTDOWN); /* pre-emptive flushing of receives */ +} + +/* + * Only called on PF0. + * + * Initialize or reset (reinit) contexts. + */ +static int lb_init_ctxts(struct hfi2_devdata *dd, u8 si) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + struct hfi2_ctxtrsrcs lbr; + int ret; + + lb_set_si_ctxtrsrcs(dd, si, &lbr); + ret = hfi2_gen_init_sctxt_pio(dd, lbd->pidx, si, lbr.first_send_context, + lbr.first_pio_block, + lbr.last_pio_block - lbr.first_pio_block); + if (ret) + goto out; + ret = hfi2_gen_init_rctxt_egr(dd, lbd->pidx, si, lbr.first_rcv_context, + lbr.first_rcvarray_entry, + lbd->b.rhq_cnt, /* all must be the same! */ + LB_DEFAULT_RCVHDRSIZE); +out: + return ret; +} + +static int lb_start_ctxts(struct hfi2_devdata *dd, u8 si) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + int ret; + + ret = hfi2_gen_start_sctxt(dd, lbd->pidx, lbd->c.first_send_context, + &lbd->b); + if (ret) + goto out; + ret = hfi2_gen_start_rctxt_egr(dd, lbd->pidx, lbd->c.first_rcv_context, + &lbd->b); +out: + return ret; +} + +/* + * Only called on PF0. + * + * Prepare VF contexts for use. This may require reset/shutdown/disable + * since contexts might still be "up" from previous run of SRIOV. + */ +static int lb_init_vfs(struct hfi2_devdata *dd) +{ + int si, nsi; + int ret = 0; + + nsi = dd->rsrcs.num_vfs + 1; + for (si = 1; si < nsi; ++si) { + ret = lb_init_ctxts(dd, si); + if (ret) + goto err_out; + } +err_out: + return ret; +} + +/* final deinit (PF0 shutdown) */ +static void lb_deinit_ctxts(struct hfi2_devdata *dd, u8 si) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + struct hfi2_ctxtrsrcs lbr; + + lb_set_si_ctxtrsrcs(dd, si, &lbr); + hfi2_gen_deinit_sctxt(dd, lbd->pidx, si, lbr.first_send_context); + hfi2_gen_deinit_rctxt(dd, lbd->pidx, si, lbr.first_rcv_context); +} + +/* + * dd->rsrcs has not been setup yet, cannot depend on it. + */ +static int lb_init(struct hfi2_devdata *dd, u8 si) +{ + struct vf2pf_lbdata *lbd; + u16 ctxt; + int ret; + + if (si == VF2PF_INIT_ALL) + return lb_init_vfs(dd); + + lbd = kzalloc_obj(lbd, GFP_KERNEL); + if (!lbd) + return -ENOMEM; + + spin_lock_init(&lbd->pio_lock); + spin_lock_init(&lbd->rcv_lock); + lbd->dd = dd; + dd->vf2pf = lbd; + /* + * This is problematic because hfi2_loopback ports are numbered + * differently depending on the number of fabric ports, and + * that varies with different chips. + */ + if (vf2pf_lb_port < dd->num_pports) + lbd->pidx = loopback_pidx_dd(dd, vf2pf_lb_port); + else if (vf2pf_lb_port < 2 * dd->num_pports) + lbd->pidx = vf2pf_lb_port; + else + lbd->pidx = loopback_pidx_dd(dd, 0); /* hope it's functional */ + lb_set_si_ctxtrsrcs(dd, si, &lbd->c); + + xa_init_flags(&lbd->tid_xa, XA_FLAGS_ALLOC); + lbd->pf0_ctxt = lbd->c.first_rcv_context - si; + + lbd->b.cr.va = dma_alloc_coherent(&dd->pcidev->dev, + sizeof(*lbd->b.cr.va), &lbd->b.cr.dma, + GFP_KERNEL); + if (!lbd->b.cr.va) { + ret = -ENOMEM; + goto err_out; + } + + /* + * rhq_cnt, rhq_ent drives the sizes of all receive allocations. + * For PF0, a larger size is justified since there might be + * several VFs communicating with PF0 at once. It is also possible + * that a VF might have a couple messages to PF0 outstanding at + * once, and thus need more than one receive buffer set. For now, + * just use the number of VFs (actually, SIs) for all cases. + * For hfi2_gen_init_rctxt_egr(), we require that all SIs use identical + * eager buffer counts. + */ + lbd->b.rhq_cnt = round_up(JKR_C_CCE_NUM_VFS + 1, HDRQ_INCREMENT); + lbd->b.egr_buf_size = PAGE_SIZE; + lbd->b.rhq_ent_size = LB_RHQ_ENT_SIZE; + lbd->b.egr.size = PAGE_ALIGN(lbd->b.rhq_cnt * lbd->b.egr_buf_size); + lbd->b.rhq.size = + PAGE_ALIGN(lbd->b.rhq_cnt * lbd->b.rhq_ent_size * sizeof(u32)); + lbd->b.rheq.size = PAGE_ALIGN(lbd->b.rhq_cnt * sizeof(u64)); + + lbd->b.egr.va = dma_alloc_coherent(&dd->pcidev->dev, lbd->b.egr.size, + &lbd->b.egr.dma, GFP_KERNEL); + lbd->b.rhq.va = dma_alloc_coherent(&dd->pcidev->dev, lbd->b.rhq.size, + &lbd->b.rhq.dma, GFP_KERNEL); + lbd->b.rheq.va = dma_alloc_coherent(&dd->pcidev->dev, lbd->b.rheq.size, + &lbd->b.rheq.dma, GFP_KERNEL); + if (!lbd->b.egr.va || !lbd->b.rhq.va || !lbd->b.rheq.va) { + ret = -ENOMEM; + goto err_out; + } + + ctxt = lbd->c.first_send_context; + lbd->pio_mem = dd->bar_maps[ctxt_bar_idx(ctxt)].piobase + + ((ctxt_bar_ctxt(ctxt) & PIO_ADDR_CONTEXT_MASK) + << PIO_ADDR_CONTEXT_SHIFT); + lbd->pio_wrap = lbd->pio_mem + + (lbd->c.last_pio_block - lbd->c.first_pio_block) * + PIO_BLOCK_SIZE; + lbd->pio_next = lbd->pio_mem; + + /* now setup contexts as required */ + if (!dd->is_vf) { + ret = lb_init_ctxts(dd, si); + if (ret) + goto err_out; + } + ret = lb_start_ctxts(dd, si); + if (ret) + goto err_out; + return 0; + +err_out: + lb_deinit(dd, si); + return ret; +} + +/* + * Called on PF0 on behalf of 'si', or for final shutdown (si == 0). + * Called on VFs only to free local resources. + */ +static void lb_deinit(struct hfi2_devdata *dd, u8 si_idx) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + + if (!lbd) + return; + + if (dd->rsrcs.si_idx != si_idx) { + int ret; + + /* PF0 performing on behalf of VF - VF2PF_STOP */ + ret = lb_init_ctxts(dd, si_idx); /* re-init */ + if (ret) + dd_dev_err( + dd, + "Failed to restart vf2pf contexts for SI %u (%d)\n", + si_idx, ret); + return; + } + lbd->flags |= VF2PF_LB_FL_SHUTDOWN; + smp_rmb(); /* ensure flags are updated before continuing */ + + lb_deinit_irq(dd); /* in case not already done */ + /* + * On VFs, can't stop contexts since we've already sent the STOP + * message to PF0 and it has likely taken ownership of them. This + * means the lb_deinit_ctxts() routines must also do the stop + * function. Only PF0 does the lb_deinit_ctxts() here. + */ + if (!dd->is_vf) + lb_deinit_ctxts(dd, dd->rsrcs.si_idx); + + if (lbd->b.cr.va) + dma_free_coherent(&dd->pcidev->dev, sizeof(*lbd->b.cr.va), + lbd->b.cr.va, lbd->b.cr.dma); + if (lbd->b.egr.va) + dma_free_coherent(&dd->pcidev->dev, lbd->b.egr.size, + lbd->b.egr.va, lbd->b.egr.dma); + if (lbd->b.rhq.va) + dma_free_coherent(&dd->pcidev->dev, lbd->b.rhq.size, + lbd->b.rhq.va, lbd->b.rhq.dma); + if (lbd->b.rheq.va) + dma_free_coherent(&dd->pcidev->dev, lbd->b.rheq.size, + lbd->b.rheq.va, lbd->b.rheq.dma); + if (!dd->is_vf) { + /* PF0 must de-init all contexts even if already done */ + int si, nsi; + + nsi = dd->rsrcs.num_vfs + 1; + for (si = 0; si < nsi; ++si) + lb_deinit_ctxts(dd, si); /* final deinit */ + } + + xa_destroy(&lbd->tid_xa); + dd->vf2pf = NULL; + kfree(lbd); +} + +static struct vf2pf_hdr *lb_get_msg(struct hfi2_devdata *dd, void *buf) +{ + struct vf2pf_lb_msg *mem = buf; + + return &mem->hdr; +} + +/* + * Allocate a unified message structure for use with hfi2_loopback implementation. + * + * Rounds total length up to qword multiple (for PIO CSR granularity). + * 'pfx' must be qword multiple to maintain memory alignment. + */ +static void *lb_msg_alloc(struct hfi2_devdata *dd, struct vf2pf_hdr **msg) +{ + struct vf2pf_lb_msg *mem; + + mem = kzalloc_obj(*mem, GFP_KERNEL); + if (mem && msg) + *msg = lb_get_msg(dd, mem); + return mem; +} + +#ifdef LB_EGRESS_WAIT +/* + * Returns number of credits outstanding for ctxt. + */ +static u32 lb_sc_crleft(struct hfi2_devdata *dd, u16 ctxt) +{ + u64 reg; + u32 curr, last; + + reg = read_sctxt_csr(dd, ctxt, dd->params->send_ctxt_credit_status_reg); + curr = (reg >> SEND_CTXT_CREDIT_STATUS_CURRENT_FREE_COUNTER_SHIFT) & + SEND_CTXT_CREDIT_STATUS_CURRENT_FREE_COUNTER_MASK; + last = reg & SEND_CTXT_CREDIT_STATUS_LAST_RETURNED_COUNTER_SMASK; + return (curr - last) & + SEND_CTXT_CREDIT_STATUS_LAST_RETURNED_COUNTER_SMASK; +} + +static bool is_sc_halted(struct hfi2_devdata *dd, u32 hw_context) +{ + return !!(read_sctxt_csr(dd, hw_context, + dd->params->send_ctxt_status_reg) & + SEND_CTXT_STATUS_CTXT_HALTED_SMASK); +} +#endif + +/* + * Send 'msg' ('len') to 'si', do not wait for response. + * + * The opaque 'msg' has a wait_queue_head_t preceding vf2pf_lb_hdr. + */ +static int lb_send(struct hfi2_devdata *dd, u8 si, void *buf) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + struct vf2pf_lb_msg *msg = buf; + struct vf2pf_lb_hdr *lbh = &msg->lbh; + struct vf2pf_hdr *hdr = &msg->hdr; + u64 *qw = (u64 *)lbh; + u64 __iomem *dst; + u32 dw_len, pbc_dw_len; + u32 qw_len, qw_wrt = 0; + u64 pbc; + u8 vl = 15; + int ret = 0; + int len; + + if (hdr->len > VF2PF_LB_MAX_MSG) + return -EINVAL; + if (!lbd || (lbd->flags & VF2PF_LB_FL_SHUTDOWN)) + return -ENXIO; + + len = hdr->len + sizeof(struct vf2pf_hdr) + sizeof(struct vf2pf_lb_hdr); + dw_len = DIV_ROUND_UP(len, sizeof(u32)); + pbc_dw_len = dw_len + (sizeof(u64) / sizeof(u32)); + qw_len = DIV_ROUND_UP(dw_len, sizeof(u64) / sizeof(u32)) + + 1; /* include PBC */ + + spin_lock(&lbd->pio_lock); + +#ifdef LB_EGRESS_WAIT + u32 loop = 0; + /* wait for (any) previous send to complete... (reset credits?) */ + while (lb_sc_crleft(dd, lbd->c.first_send_context)) { + if (is_sc_halted(dd, lbd->c.first_send_context)) { + ret = -EIO; + goto out; + } + if (loop > 100) { + ret = -ETIME; + goto out; + } + ++loop; + mdelay(1); + } +#endif + dst = lbd->pio_next + SOP_DISTANCE; + lbd->pio_next += + round_up(len + sizeof(u64), PIO_BLOCK_SIZE); /* incl. PBC */ + if (lbd->pio_next >= lbd->pio_wrap) + lbd->pio_next -= (lbd->pio_wrap - lbd->pio_mem); + spin_unlock(&lbd->pio_lock); + + /* force these to legit value */ + hdr->si = dd->rsrcs.si_idx; + + lbh->lrh[0] = cpu_to_be16(HFI2_LRH_BTH | (vl << 12)); + lbh->lrh[1] = cpu_to_be16(IB_LID_PERMISSIVE); + lbh->lrh[2] = cpu_to_be16(dw_len + SIZE_OF_CRC); + lbh->lrh[3] = cpu_to_be16(IB_LID_PERMISSIVE); + lbh->bth[0] = cpu_to_be32(IB_OPCODE_UD_SEND_ONLY << 24); /* used? */ + lbh->bth[1] = + cpu_to_be32((RVT_KDETH_QP_PREFIX << RCV_BTH_QP_KDETH_QP_SHIFT) | + (lbd->pf0_ctxt + si)); + lbh->bth[2] = cpu_to_be32(0 /*loopback_dst_vf_index*/); /* PSN 0 */ + ; + lbh->ver_tid_offset = (1 << KDETH_KVER_SHIFT); + /* lbh->jkey should be dont-care since checking is OFF */ + /* lbh->hcrc generated by h/w */ + + pbc = hfi2_gen_create_pbc_pidx(lbd->pidx, 0, 0, vl, pbc_dw_len, PBC_L2_9B, + OPA_LID_PERMISSIVE, + lbd->c.first_send_context); + pbc &= ~PBC_INSERT_HCRC_SMASK; + pbc |= (u64)PBC_IHCRC_LKDETH << PBC_INSERT_HCRC_SHIFT; + + /* + * First block (8 qwords) written with SOP_DISTANCE set, + * the rest with SOP_DISTANCE clear. Must write whole blocks. + * First block (SOP) never requires wrap. + */ + writeq(pbc, dst++); + ++qw_wrt; + while (qw_wrt < qw_len) { + writeq(*qw++, dst++); + ++qw_wrt; + if (qw_wrt >= 8) { + if (qw_wrt == 8) + dst = (void *)dst - SOP_DISTANCE; + if ((void *)dst >= lbd->pio_wrap) + dst = lbd->pio_mem; /* never needs SOP_DISTANCE */ + } + } + while (qw_wrt & 7) { + writeq(0, dst++); + ++qw_wrt; + } + return ret; +} + +/* + * Only called for VFs. + * Returns 0 on error (invalid VF SI). + * Must not depend on any setup (hfi2_vf2pf_init() has not been called). + * BARs have been mapped. + */ +static int lb_probe_si(struct hfi2_devdata *dd) +{ + int nctxt; + u64 reg; + u16 pf0_ctxt; + int si; + + nctxt = vf2pf_lb_dev.num_ctxts; + if (!nctxt) + return 0; + + pf0_ctxt = chip_rcv_contexts(dd) - nctxt; + /* + * Except for SI 1, this will cause CSR Access Violations + * so we need to clear that after - regardless of the result. + */ + for (si = 1; si <= JKR_C_CCE_NUM_VFS; ++si) { + reg = read_kctxt_csr(dd, pf0_ctxt + si, + dd->params->rcv_hdr_ent_size_reg); + if (reg) { + dd_dev_info(dd, "Probed SI index %d\n", si); + goto found; + } + } + si = 0; /* not found */ +found: + /* clear any access violations */ + hfi2_write_csr(dd, JKR_CCE_ERR_INFO_ACCESS_VIOLATION, + JKR_CCE_ERR_INFO_ACCESS_VIOLATION_VALID_SMASK); + return si; +} + +static ssize_t vf2pf_lb_debug_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct hfi2_ibdev *dev = + rdma_device_to_drv_device(device, struct hfi2_ibdev, rdi.ibdev); + struct hfi2_devdata *dd = dd_from_dev(dev); + struct vf2pf_lbdata *lbd = dd->vf2pf; + ssize_t off = 0; + + if (!lbd) + return 0; + off += sysfs_emit_at(buf, off, + "rctxt %u\n" + "sctxt %u\n" + "rcvary %u-%u\n" + "pio %u-%u\n", + lbd->c.first_rcv_context, + lbd->c.first_send_context, + lbd->c.first_rcvarray_entry, + lbd->c.last_rcvarray_entry - 1, + lbd->c.first_pio_block, lbd->c.last_pio_block - 1); + off += sysfs_emit_at(buf, off, + "pidx %u\n" + "pf0_ctxt %u\n" + "tid_next %u\n" + "CR %p %016llx\n" + "PIO mem %p wrap %p next %p\n", + lbd->pidx, lbd->pf0_ctxt, lbd->tid_next, + lbd->b.cr.va, lbd->b.cr.dma, lbd->pio_mem, + lbd->pio_wrap, lbd->pio_next); + off += sysfs_emit_at(buf, off, + "rcv_irq %u\n" + "egr size %lu va %p dma %016llx\n" + "rhq size %lu va %p dma %016llx\n" + "rheq size %lu va %p dma %016llx\n", + lbd->rcv_irq, lbd->b.egr.size, lbd->b.egr.va, + lbd->b.egr.dma, lbd->b.rhq.size, lbd->b.rhq.va, + lbd->b.rhq.dma, lbd->b.rheq.size, lbd->b.rheq.va, + lbd->b.rheq.dma); + + if (off >= PAGE_SIZE) { + dd_dev_warn(dd, "%s exceeds PAGE_SIZE.\n", attr->attr.name); + return -EFBIG; + } + return off; +} + +static DEVICE_ATTR_RO(vf2pf_lb_debug); + +static ssize_t vf2pf_lb_reset_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hfi2_ibdev *dev = + rdma_device_to_drv_device(device, struct hfi2_ibdev, rdi.ibdev); + struct hfi2_devdata *dd = dd_from_dev(dev); + unsigned long si; + int ret; + + ret = kstrtoul(buf, 0, &si); + if (ret || !si || si > dd->rsrcs.num_vfs) + return -EINVAL; + + lb_deinit(dd, (u8)si); + return count; +} + +static DEVICE_ATTR_WO(vf2pf_lb_reset); + +static void lb_init_sysfs(struct hfi2_devdata *dd, struct device *class_dev) +{ + int ret; + + ret = sysfs_create_file(&class_dev->kobj, + &dev_attr_vf2pf_lb_debug.attr); + if (ret) + dd_dev_warn(dd, "failed to create sysfs attr %s (%d)\n", + dev_attr_vf2pf_lb_debug.attr.name, ret); + if (!dd->is_vf) { + ret = sysfs_create_file(&class_dev->kobj, + &dev_attr_vf2pf_lb_reset.attr); + if (ret) + dd_dev_warn(dd, "failed to create sysfs attr %s (%d)\n", + dev_attr_vf2pf_lb_reset.attr.name, ret); + } +} + +/* for additional output to "hw_resources" */ +static int lb_sysfs_emit_at(struct hfi2_devdata *dd, char *buf, int at) +{ + struct vf2pf_lbdata *lbd = dd->vf2pf; + int off = at; + + if (!lbd) + return 0; + + off += sysfs_emit_at(buf, off, + "lb.rctxt %u\n" + "lb.sctxt %u\n" + "lb.rcvary %u-%u\n" + "lb.pio %u-%u\n", + lbd->c.first_rcv_context, + lbd->c.first_send_context, + lbd->c.first_rcvarray_entry, + lbd->c.last_rcvarray_entry - 1, + lbd->c.first_pio_block, lbd->c.last_pio_block - 1); + + return off - at; +} + +static void lb_set_si_enables(struct hfi2_devdata *dd, int si, u64 *csrs, + void (*si_enables)(struct hfi2_devdata *dd, + u64 *csrs, u32 start, u32 end)) +{ + struct hfi2_ctxtrsrcs lbr; + + lb_set_si_ctxtrsrcs(dd, si, &lbr); + si_enables(dd, csrs, + dd->params->is_rcvavail_start + lbr.first_rcv_context, + dd->params->is_rcvavail_start + lbr.last_rcv_context); +} + +static struct vf2pf_devops vf2pf_lb_dev = { + .num_ctxts = JKR_C_CCE_NUM_VFS + 1, + .num_irq = 1, + .init = lb_init, + .deinit = lb_deinit, + .send = lb_send, + .msg_alloc = lb_msg_alloc, + .set_tid = lb_set_tid, + .get_tid = lb_get_tid, + .get_msg = lb_get_msg, + .probe_si = lb_probe_si, + .init_sysfs = lb_init_sysfs, + .sysfs_emit_at = lb_sysfs_emit_at, + .init_irq = lb_init_irq, + .deinit_irq = lb_deinit_irq, + .rcv_wait = lb_rcv_wait, + .set_si_enables = lb_set_si_enables, +}; + +struct vf2pf_devops *hfi2_get_lb_devops(void) +{ + return &vf2pf_lb_dev; +}