/* * t19x-nvlink-endpt-isr.c: * This file contains interrupt handling code for the Tegra NVLINK controller. * * Copyright (c) 2017-2018, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "t19x-nvlink-endpt.h" #include "nvlink-hw.h" /* Enable minion falcon Interrupts and route to Host */ void nvlink_config_minion_falcon_intr(struct tnvlink_dev *tdev) { u32 reg_val = 0; /* Enable interrupts. Writing a '1' to any bit in IRQMSET * will set the corresponding bit in IRQMASK. */ reg_val = BIT(CMINION_FALCON_IRQMSET_WDTMR) | BIT(CMINION_FALCON_IRQMSET_HALT) | BIT(CMINION_FALCON_IRQMSET_EXTERR); nvlw_minion_writel(tdev, CMINION_FALCON_IRQMSET, reg_val); /* interrrupts destination setting to HOST */ reg_val = BIT(CMINION_FALCON_IRQDEST_HOST_WDTMR) | BIT(CMINION_FALCON_IRQDEST_HOST_HALT) | BIT(CMINION_FALCON_IRQDEST_HOST_EXTERR); /* Send the interrupts on the "normal" interrupt lines to host */ reg_val &= ~(BIT(CMINION_FALCON_IRQDEST_TARGET_WDTMR) | BIT(CMINION_FALCON_IRQDEST_TARGET_HALT) | BIT(CMINION_FALCON_IRQDEST_TARGET_EXTERR)); nvlw_minion_writel(tdev, CMINION_FALCON_IRQDEST, reg_val); } /* Configure NVLW interrupts */ static void nvlw_config_intr(struct tnvlink_dev *tdev) { u32 reg_val = 0; /* Configure non link specific common registers */ reg_val = BIT(NVLW_COMMON_INTR_0_MASK_FATAL); nvlw_tioctrl_writel(tdev, NVLW_COMMON_INTR_0_MASK, reg_val); reg_val = BIT(NVLW_COMMON_INTR_1_MASK_NONFATAL) | BIT(NVLW_COMMON_INTR_1_MASK_CORRECTABLE); nvlw_tioctrl_writel(tdev, NVLW_COMMON_INTR_1_MASK, reg_val); reg_val = BIT(NVLW_COMMON_INTR_2_MASK_INTRA) | BIT(NVLW_COMMON_INTR_2_MASK_INTRB); nvlw_tioctrl_writel(tdev, NVLW_COMMON_INTR_2_MASK, reg_val); /* Configure link specific registers */ reg_val = BIT(NVLW_LINK_INTR_0_MASK_FATAL); nvlw_tioctrl_writel(tdev, NVLW_LINK_INTR_0_MASK, reg_val); reg_val = BIT(NVLW_LINK_INTR_1_MASK_NONFATAL) | BIT(NVLW_LINK_INTR_1_MASK_CORRECTABLE); nvlw_tioctrl_writel(tdev, NVLW_LINK_INTR_1_MASK, reg_val); reg_val = BIT(NVLW_LINK_INTR_2_MASK_INTRA) | BIT(NVLW_LINK_INTR_2_MASK_INTRB); nvlw_tioctrl_writel(tdev, NVLW_LINK_INTR_2_MASK, reg_val); } /* Initialize NVLIPT common interrupts */ static void nvlipt_config_common_intr(struct tnvlink_dev *tdev) { u32 reg_val = 0; /* Allow all common types to be routed up * and out on tree0 and 1 */ reg_val = BIT(NVLIPT_INTR_CONTROL_COMMON_STALLENABLE) | BIT(NVLIPT_INTR_CONTROL_COMMON_NOSTALLENABLE); nvlw_nvlipt_writel(tdev, NVLIPT_INTR_CONTROL_COMMON, reg_val); reg_val = nvlw_nvlipt_readl(tdev, NVLIPT_ERR_UC_MASK_LINK0); reg_val &= ~BIT(NVLIPT_ERR_UC_MASK_LINK0_UCINTERNAL); nvlw_nvlipt_writel(tdev, NVLIPT_ERR_UC_MASK_LINK0, reg_val); reg_val = nvlw_nvlipt_readl(tdev, NVLIPT_ERR_UC_SEVERITY_LINK0); reg_val |= BIT(NVLIPT_ERR_UC_SEVERITY_LINK0_UCINTERNAL); nvlw_nvlipt_writel(tdev, NVLIPT_ERR_UC_SEVERITY_LINK0, reg_val); reg_val = nvlw_nvlipt_readl(tdev, NVLIPT_ERR_C_MASK_LINK0); reg_val &= ~BIT(NVLIPT_ERR_C_MASK_LINK0_CINTERNAL); nvlw_nvlipt_writel(tdev, NVLIPT_ERR_C_MASK_LINK0, reg_val); reg_val = nvlw_nvlipt_readl(tdev, NVLIPT_ERR_CONTROL_LINK0); reg_val |= BIT(NVLIPT_ERR_CONTROL_LINK0_FATALENABLE) | BIT(NVLIPT_ERR_CONTROL_LINK0_CORRECTABLEENABLE); nvlw_nvlipt_writel(tdev, NVLIPT_ERR_CONTROL_LINK0, reg_val); } /* Initialize MINION common interrupts */ static void minion_config_common_intr(struct tnvlink_dev *tdev) { u32 reg_val = 0; /* Tree 1 (non-stall) is disabled until there is a need */ nvlw_minion_writel(tdev, MINION_MINION_INTR_NONSTALL_EN, 0); /* Tree 0 (stall) is where we route all MINION interrupts for now */ reg_val = BIT(MINION_MINION_INTR_STALL_EN_FATAL) | BIT(MINION_MINION_INTR_STALL_EN_NONFATAL) | BIT(MINION_MINION_INTR_STALL_EN_FALCON_STALL) | BIT(MINION_MINION_INTR_STALL_EN_FALCON_NOSTALL); nvlw_minion_writel(tdev, MINION_MINION_INTR_STALL_EN, reg_val); } void nvlink_config_common_intr(struct tnvlink_dev *tdev) { nvlw_config_intr(tdev); nvlipt_config_common_intr(tdev); minion_config_common_intr(tdev); } /* Enable MINION link interrupts */ static void nvlink_enable_minion_link_intr(struct tnvlink_dev *tdev) { u32 reg_val = 0; /* Tree 0 (stall) only support for now */ reg_val = nvlw_minion_readl(tdev, MINION_MINION_INTR_STALL_EN); reg_val |= MINION_MINION_INTR_STALL_EN_LINK( MINION_MINION_INTR_STALL_EN_LINK_ENABLE_ALL); nvlw_minion_writel(tdev, MINION_MINION_INTR_STALL_EN, reg_val); } void nvlink_enable_dl_interrupts(struct tnvlink_dev *tdev) { u32 reg_val = 0; /* Clear interrupt register to get rid of any stale state. (W1C) */ nvlw_nvl_writel(tdev, NVL_INTR, 0xffffffff); nvlw_nvl_writel(tdev, NVL_INTR_SW2, 0xffffffff); /* Recommend non-fatal interrupt line. * This indicates that we have seen a significant number of bit errors * and need help. This will be flagged while we are still in the process * of transitioning to SWCFG. */ reg_val |= BIT(NVL_INTR_STALL_EN_TX_RECOVERY_LONG); /* Recommend fatal. Internal hardware parity fault. Reset required */ reg_val |= BIT(NVL_INTR_STALL_EN_TX_FAULT_RAM); reg_val |= BIT(NVL_INTR_STALL_EN_TX_FAULT_INTERFACE); /* Recommend fatal, should never happen? */ reg_val |= BIT(NVL_INTR_STALL_EN_TX_FAULT_SUBLINK_CHANGE); /* Recommend to not enable interrupt OR treat similar to RECOVERY_LONG. * HW should end up failing and going to SWCFG. */ reg_val |= BIT(NVL_INTR_STALL_EN_RX_FAULT_SUBLINK_CHANGE); /* Recommend fatal, should not happen except through software error * with AN0 injection mechanism */ reg_val |= BIT(NVL_INTR_STALL_EN_RX_FAULT_DL_PROTOCOL); /* Recommend fatal (after initialization completed). * Internal hardware parity fault or other very bad condition, * link unusable. * EXCEPT during INIT->HWCFG where it indicates a failure * to get to safe mode (may still be fatal but retry) */ reg_val |= BIT(NVL_INTR_STALL_EN_LTSSM_FAULT); /* Enable Stall interrupts */ nvlw_nvl_writel(tdev, NVL_INTR_STALL_EN, reg_val); /* TODO: Check if below WAR is still needed */ /* Configure the error threshold that generates interrupt as a WAR * for bug 1710544 */ reg_val = nvlw_nvl_readl(tdev, NVL_SL1_ERROR_RATE_CTRL); reg_val |= NVL_SL1_ERROR_RATE_CTRL_SHORT_THRESHOLD_MAN_F(0x2); reg_val |= NVL_SL1_ERROR_RATE_CTRL_LONG_THRESHOLD_MAN_F(0x2); nvlw_nvl_writel(tdev, NVL_SL1_ERROR_RATE_CTRL, reg_val); /* Don't hookup interrupts on NON-STALL line */ nvlw_nvl_writel(tdev, NVL_INTR_NONSTALL_EN, 0); } /* Enable TLC link interrupts */ static void nvlink_enable_tl_interrupts(struct tnvlink_dev *tdev) { u32 reg_val = 0; /* Enable TLC RX interrupts */ reg_val = BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXDLHDRPARITYERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXDLDATAPARITYERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXDLCTRLPARITYERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXRAMDATAPARITYERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXRAMHDRPARITYERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXINVALIDAEERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXINVALIDBEERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXINVALIDADDRALIGNERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXPKTLENERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RSVCMDENCERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RSVDATLENENCERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RSVADDRTYPEERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RSVRSPSTATUSERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RSVPKTSTATUSERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RSVCACHEATTRPROBEREQERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RSVCACHEATTRPROBERSPERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_DATLENGTATOMICREQMAXERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_DATLENGTRMWREQMAXERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_DATLENLTATRRSPMINERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_INVALIDCACHEATTRPOERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_INVALIDCRERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_0_RXRESPSTATUSTARGETERR) | BIT( NVLTLC_RX_ERR_REPORT_EN_0_RXRESPSTATUSUNSUPPORTEDREQUESTERR); nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_REPORT_EN_0, reg_val); reg_val = 0; reg_val |= NVLTLC_RX_ERR_REPORT_EN_1_RXHDROVFERR_F(0xFF); reg_val |= NVLTLC_RX_ERR_REPORT_EN_1_RXDATAOVFERR_F(0xFF); reg_val |= BIT(NVLTLC_RX_ERR_REPORT_EN_1_STOMPDETERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_1_RXPOISONERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_1_CORRECTABLEINTERNALERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_1_RXUNSUPVCOVFERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_1_RXUNSUPNVLINKCREDITRELERR) | BIT(NVLTLC_RX_ERR_REPORT_EN_1_RXUNSUPNCISOCCREDITRELERR); nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_REPORT_EN_1, reg_val); /* Enable TLC TX interrupts */ reg_val = 0; reg_val |= NVLTLC_TX_ERR_REPORT_EN_0_TXHDRCREDITOVFERR_F(0xFF); reg_val |= NVLTLC_TX_ERR_REPORT_EN_0_TXDATACREDITOVFERR_F(0xFF); reg_val |= BIT(NVLTLC_TX_ERR_REPORT_EN_0_TXDLCREDITOVFERR) | BIT(NVLTLC_TX_ERR_REPORT_EN_0_TXDLCREDITPARITYERR) | BIT(NVLTLC_TX_ERR_REPORT_EN_0_TXRAMHDRPARITYERR) | BIT(NVLTLC_TX_ERR_REPORT_EN_0_TXRAMDATAPARITYERR) | BIT(NVLTLC_TX_ERR_REPORT_EN_0_TXUNSUPVCOVFERR) | BIT(NVLTLC_TX_ERR_REPORT_EN_0_TXSTOMPDET) | BIT(NVLTLC_TX_ERR_REPORT_EN_0_TXPOISONDET) | BIT(NVLTLC_TX_ERR_REPORT_EN_0_TARGETERR) | BIT(NVLTLC_TX_ERR_REPORT_EN_0_UNSUPPORTEDREQUESTERR); nvlw_nvltlc_writel(tdev, NVLTLC_TX_ERR_REPORT_EN_0, reg_val); } static void nvlink_enable_sync2x_interrupts(struct tnvlink_dev *tdev) { u32 reg_val = 0; reg_val = nvlw_sync2x_readl(tdev, NVSYNC2X_ECCPARITY_CTRL); reg_val |= BIT(NVSYNC2X_ECCPARITY_CTRL_RX_PARITY_CTRL2_ENB) | BIT(NVSYNC2X_ECCPARITY_CTRL_TX_ECCENB0) | BIT(NVSYNC2X_ECCPARITY_CTRL_TX_PARITY_CTRL1_ENB) | BIT(NVSYNC2X_ECCPARITY_CTRL_TX_ECCPARITYCOUNTERENB0) | BIT(NVSYNC2X_ECCPARITY_CTRL_TX_ECCPARITYCOUNTSINGLEBIT0); nvlw_sync2x_writel(tdev, NVSYNC2X_ECCPARITY_CTRL, reg_val); /* Set TX ECC parity error limit */ reg_val = nvlw_sync2x_readl(tdev, NVSYNC2X_TX_ECCPARITY_ERROR_LIMIT); reg_val &= ~NVSYNC2X_TX_ECCPARITY_ERROR_LIMIT_ERROR_LIMIT_F(~0); reg_val |= NVSYNC2X_TX_ECCPARITY_ERROR_LIMIT_ERROR_LIMIT_F(0x100); nvlw_sync2x_writel(tdev, NVSYNC2X_TX_ECCPARITY_ERROR_LIMIT, reg_val); /* Enable logging of the errors */ reg_val = nvlw_sync2x_readl(tdev, NVSYNC2X_ERR_LOG_EN_0); reg_val |= BIT(NVSYNC2X_ERR_LOG_EN_0_RXPARITYCTRL2ERR) | BIT(NVSYNC2X_ERR_LOG_EN_0_TXECCPARITYLIMITERR) | BIT(NVSYNC2X_ERR_LOG_EN_0_TXECCHDRDOUBLEBITERR) | BIT(NVSYNC2X_ERR_LOG_EN_0_TXECCDATADOUBLEBITERR) | BIT(NVSYNC2X_ERR_LOG_EN_0_TXPARITYCTRL0ERR) | BIT(NVSYNC2X_ERR_LOG_EN_0_TXPARITYCTRL1ERR); nvlw_sync2x_writel(tdev, NVSYNC2X_ERR_LOG_EN_0, reg_val); /* Enable interrupt generation on logged errors */ reg_val = nvlw_sync2x_readl(tdev, NVSYNC2X_ERR_REPORT_EN_0); reg_val |= BIT(NVSYNC2X_ERR_REPORT_EN_0_RXPARITYCTRL2ERR) | BIT(NVSYNC2X_ERR_REPORT_EN_0_TXECCPARITYLIMITERR) | BIT(NVSYNC2X_ERR_REPORT_EN_0_TXECCHDRDOUBLEBITERR) | BIT(NVSYNC2X_ERR_REPORT_EN_0_TXECCDATADOUBLEBITERR) | BIT(NVSYNC2X_ERR_REPORT_EN_0_TXPARITYCTRL0ERR) | BIT(NVSYNC2X_ERR_REPORT_EN_0_TXPARITYCTRL1ERR); nvlw_sync2x_writel(tdev, NVSYNC2X_ERR_REPORT_EN_0, reg_val); /* Enable freezing of the link interface on logged errors */ reg_val = nvlw_sync2x_readl(tdev, NVSYNC2X_ERR_CONTAIN_EN_0); reg_val |= BIT(NVSYNC2X_ERR_CONTAIN_EN_0_RXPARITYCTRL2ERR) | BIT(NVSYNC2X_ERR_CONTAIN_EN_0_TXECCHDRDOUBLEBITERR) | BIT(NVSYNC2X_ERR_CONTAIN_EN_0_TXECCDATADOUBLEBITERR) | BIT(NVSYNC2X_ERR_CONTAIN_EN_0_TXPARITYCTRL0ERR) | BIT(NVSYNC2X_ERR_CONTAIN_EN_0_TXPARITYCTRL1ERR); nvlw_sync2x_writel(tdev, NVSYNC2X_ERR_CONTAIN_EN_0, reg_val); } /* Enable NVLIPT Link interrupts */ static void nvlink_enable_nvlipt_interrupts(struct tnvlink_dev *tdev) { u32 reg_val; /* * Enable stall/nonstall interrupts to host for this link. * This is a rollup of all interrupts in all devices for * this link and is required for any interrupts to be handled by SW. */ reg_val = nvlw_nvlipt_readl(tdev, NVLIPT_INTR_CONTROL_LINK0); reg_val |= BIT(NVLIPT_INTR_CONTROL_LINK0_STALLENABLE); reg_val |= BIT(NVLIPT_INTR_CONTROL_LINK0_NOSTALLENABLE); nvlw_nvlipt_writel(tdev, NVLIPT_INTR_CONTROL_LINK0, reg_val); } /* Enable link interrupts */ void nvlink_enable_link_interrupts(struct tnvlink_dev *tdev) { nvlink_enable_minion_link_intr(tdev); nvlink_enable_dl_interrupts(tdev); nvlink_enable_tl_interrupts(tdev); nvlink_enable_sync2x_interrupts(tdev); nvlink_enable_nvlipt_interrupts(tdev); } /* Disable NVLIPT Link interrupts */ static void nvlink_disable_nvlipt_interrupts(struct tnvlink_dev *tdev) { u32 reg_val; reg_val = nvlw_nvlipt_readl(tdev, NVLIPT_INTR_CONTROL_LINK0); reg_val &= ~BIT(NVLIPT_INTR_CONTROL_LINK0_STALLENABLE); reg_val &= ~BIT(NVLIPT_INTR_CONTROL_LINK0_NOSTALLENABLE); nvlw_nvlipt_writel(tdev, NVLIPT_INTR_CONTROL_LINK0, reg_val); } /* Disable MINION FALCON interrupts */ static void nvlink_minion_disable_falcon_interrupts(struct tnvlink_dev *tdev) { u32 reg_data; reg_data = nvlw_minion_readl(tdev, MINION_MINION_INTR_STALL_EN); reg_data &= ~BIT(MINION_MINION_INTR_STALL_EN_FATAL); reg_data &= ~BIT(MINION_MINION_INTR_STALL_EN_NONFATAL); reg_data &= ~BIT(MINION_MINION_INTR_STALL_EN_FALCON_STALL); reg_data &= ~BIT(MINION_MINION_INTR_STALL_EN_FALCON_NOSTALL); nvlw_minion_writel(tdev, MINION_MINION_INTR_STALL_EN, reg_data); } /* Service MINION Falcon interrupts */ void minion_service_falcon_intr(struct tnvlink_dev *tdev) { u32 irq_stat = 0; u32 irq_mask = 0; u32 interrupts = 0; u32 clear_bits = 0; /* * Get the current IRQ status and mask for the sources not directed to * host */ irq_stat = nvlw_minion_readl(tdev, CMINION_FALCON_IRQSTAT); irq_mask = nvlw_minion_readl(tdev, CMINION_FALCON_IRQMASK); interrupts = irq_stat & irq_mask; /* Exit if there is nothing to do */ if (interrupts == 0) return; /* Service the pending interrupt(s) */ if (interrupts & BIT(CMINION_FALCON_IRQSTAT_WDTMR)) { nvlink_err("Received MINION Falcon WDTMR interrupt"); clear_bits |= BIT(CMINION_FALCON_IRQSTAT_WDTMR); } if (interrupts & BIT(CMINION_FALCON_IRQSTAT_HALT)) { nvlink_err("Received MINION Falcon HALT interrupt"); clear_bits |= BIT(CMINION_FALCON_IRQSTAT_HALT); } if (interrupts & BIT(CMINION_FALCON_IRQSTAT_EXTERR)) { nvlink_err("Received MINION Falcon EXTERR interrupt"); clear_bits |= BIT(CMINION_FALCON_IRQSTAT_EXTERR); } /* We are considering all falcon interrupts as fatal. * Disable MINION Falcon interrupts. */ nvlink_minion_disable_falcon_interrupts(tdev); nvlink_err("MINION Falcon interrupts disabled due to fatal interrupt"); /* Clear interrupt (W1C) */ nvlw_minion_writel(tdev, CMINION_FALCON_IRQSCLR, clear_bits); } /* Service MINION FATAL notification interrupt */ static bool minion_service_fatal_intr(struct tnvlink_dev *tdev) { nvlink_dbg("Received MINION Falcon FATAL notification interrupt"); /* Disable interrupts - cannot recover */ nvlink_minion_disable_falcon_interrupts(tdev); nvlink_err("MINION Falcon interrupts disabled due to fatal" " notification interrupt"); /* Clear interrupt (W1C) */ nvlw_minion_writel(tdev, MINION_MINION_INTR, BIT(MINION_MINION_INTR_FATAL)); return 0; } /* Service MINION NONFATAL notification interrupt */ static bool minion_service_non_fatal_intr(struct tnvlink_dev *tdev) { nvlink_dbg("Received MINION Falcon NONFATAL notification interrupt"); /* Clear interrupt (W1C) */ nvlw_minion_writel(tdev, MINION_MINION_INTR, BIT(MINION_MINION_INTR_NONFATAL)); return 0; } /* Disable MINION link interrupts */ static void minion_disable_link_intr(struct tnvlink_dev *tdev) { u32 intr_en; /* Tree 0 (stall) only support for now */ intr_en = nvlw_minion_readl(tdev, MINION_MINION_INTR_STALL_EN); intr_en &= ~MINION_MINION_INTR_STALL_EN_LINK( MINION_MINION_INTR_STALL_EN_LINK_ENABLE_ALL); nvlw_minion_writel(tdev, MINION_MINION_INTR_STALL_EN, intr_en); } /* Service MINION link interrupts */ static bool minion_service_link_intr(struct tnvlink_dev *tdev) { u32 link_intr = nvlw_minion_readl(tdev, MINION_NVLINK_LINK_INTR); bool fatal_interrupt = false; int intr_code; nvlink_dbg("NVLink MINION Link Interrupt: MINION_NVLINK_LINK_INTR 0x%x", link_intr); intr_code = (link_intr & MINION_NVLINK_LINK_INTR_CODE_MASK) >> MINION_NVLINK_LINK_INTR_CODE_SHIFT; switch (intr_code) { /* The following are considered NON-FATAL by arch */ case MINION_NVLINK_LINK_INTR_CODE_SWREQ: nvlink_dbg("Received NON-FATAL INTR_CODE = SWREQ"); break; /* The following are considered FATAL by arch */ case MINION_NVLINK_LINK_INTR_CODE_NA: nvlink_dbg("Received FATAL INTR_CODE = NA"); fatal_interrupt = true; break; case MINION_NVLINK_LINK_INTR_CODE_DLREQ: nvlink_dbg("Received FATAL INTR_CODE = DLREQ"); fatal_interrupt = true; break; default: nvlink_dbg("Received UNKNOWN INTR_CODE = 0x%x", intr_code); fatal_interrupt = true; break; } /* On fatal interrupts, disable interrupts for that link */ if (fatal_interrupt) { minion_disable_link_intr(tdev); nvlink_err("NVLink MINION link interrupts disabled due to fatal" " MINION error: INTR_CODE = 0x%x", intr_code); } /* Clear the interrupt state and move on */ link_intr |= BIT(MINION_NVLINK_LINK_INTR_STATE); nvlw_minion_writel(tdev, MINION_NVLINK_LINK_INTR, link_intr); return true; } /* Service MINION interrupts */ static bool nvlink_minion_service_intr(struct tnvlink_dev *tdev) { u32 interrupts; u32 interrupting_links; /* Currently we only handle tree 0 */ /* Filter any interrupts against selected tree */ interrupts = nvlw_minion_readl(tdev, MINION_MINION_INTR) & nvlw_minion_readl(tdev, MINION_MINION_INTR_STALL_EN); /* Service Falcon interrupts before we process engine interrutps */ if (interrupts & (BIT(MINION_MINION_INTR_FALCON_STALL) | BIT(MINION_MINION_INTR_FALCON_NOSTALL))) minion_service_falcon_intr(tdev); /* Process ucode->driver FATAL notifications */ if (interrupts & BIT(MINION_MINION_INTR_FATAL)) minion_service_fatal_intr(tdev); /* Process ucode->driver NONFATAL notifications */ if (interrupts & BIT(MINION_MINION_INTR_NONFATAL)) minion_service_non_fatal_intr(tdev); /* Process interrupting links */ interrupting_links = MINION_MINION_INTR_LINK_V(interrupts); if (interrupting_links & 1) minion_service_link_intr(tdev); interrupts = nvlw_minion_readl(tdev, MINION_MINION_INTR) & nvlw_minion_readl(tdev, MINION_MINION_INTR_STALL_EN); return (interrupts == 0); } /* Disable DL/PL interrupts */ void nvlink_disable_dl_interrupts(struct tnvlink_dev *tdev) { nvlw_nvl_writel(tdev, NVL_INTR_NONSTALL_EN, 0); nvlw_nvl_writel(tdev, NVL_INTR_STALL_EN, 0); } /* Disable TLC interrupts */ static void nvlink_disable_tl_interrupts(struct tnvlink_dev *tdev) { /* Disable TLC RX interrupts */ nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_REPORT_EN_0, 0); nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_REPORT_EN_1, 0); /* Disable TLC TX interrupts */ nvlw_nvltlc_writel(tdev, NVLTLC_TX_ERR_REPORT_EN_0, 0); } /* Handles errors reported on a link. This will disable link interrupts * for fatal, non-injected interrupts on the device that reports them */ static void nvlink_handle_link_errors(struct tnvlink_dev *tdev, struct nvlink_link_error_masks *err_masks, u64 inforom_mask) { /* Ignore injected errors */ if (err_masks->tl_injected || err_masks->tlc_rx0_injected || err_masks->tlc_rx1_injected || err_masks->tlc_tx_injected) nvlink_dbg("Ignoring injected errors for link"); /* Disable interrupts after fatal errors */ if (err_masks->tl || err_masks->tlc_rx0 || err_masks->tlc_rx1 || err_masks->tlc_tx) { nvlink_disable_tl_interrupts(tdev); } if (err_masks->dl) nvlink_disable_dl_interrupts(tdev); /* Log publicly if a fatal NVLink error has occurred - these are never * expected. */ if (inforom_mask) { nvlink_err("fatal error detected, inforom 0x%llx", inforom_mask); /* TODO : * Log a set of fatal errors that have occurred on the given * link to the NVL object in the InfoROM. */ } } int nvlink_service_dl_interrupts(struct tnvlink_dev *tdev, bool *retrain_from_safe) { u32 nonfatal_mask = 0; u32 fatal_mask = 0; u32 inforom_mask = 0; u32 intr_status = 0; int ret = 0; struct nvlink_link_error_masks err_masks = {0}; *retrain_from_safe = false; /* * Mask DLPL intr register while reading it. This ensures that we * operate only on enabled interrupt bits. HW triggers interrupts when * (NVL_INTR & NVL_INTR_STALL_EN) is non-zero. * Hence, SW needs to follow the same masking logic to filter out * interrupts. */ intr_status = nvlw_nvl_readl(tdev, NVL_INTR) & nvlw_nvl_readl(tdev, NVL_INTR_STALL_EN); if (intr_status & BIT(NVL_INTR_TX_REPLAY)) { nvlink_err("Non Fatal: TX Replay DL interrupt hit on link"); nonfatal_mask |= BIT(NVL_INTR_TX_REPLAY); } if (intr_status & BIT(NVL_INTR_TX_RECOVERY_SHORT)) { nvlink_err("Non Fatal: TX Recovery Short DL interrupt hit" " on link"); nonfatal_mask |= BIT(NVL_INTR_TX_RECOVERY_SHORT); } if (intr_status & BIT(NVL_INTR_TX_RECOVERY_LONG)) { nvlink_err("Fatal: TX Recovery Long DL interrupt hit on link"); nvlink_err("Retraining from SAFE"); nonfatal_mask |= BIT(NVL_INTR_TX_RECOVERY_LONG); inforom_mask |= BIT(DL_TX_RECOVERY_LONG); *retrain_from_safe = true; } if (intr_status & BIT(NVL_INTR_TX_FAULT_RAM)) { nvlink_err("Fatal: TX Fault RAM DL interrupt hit on link"); nvlink_err("Reset Required"); fatal_mask |= BIT(NVL_INTR_TX_FAULT_RAM); inforom_mask |= BIT(DL_TX_FAULT_RAM); } if (intr_status & BIT(NVL_INTR_TX_FAULT_INTERFACE)) { nvlink_err("Fatal: TX Fault Interface DL interrupt hit on" " link"); nvlink_err("Reset Required"); fatal_mask |= BIT(NVL_INTR_TX_FAULT_INTERFACE); inforom_mask |= BIT(DL_TX_FAULT_INTERFACE); } if (intr_status & BIT(NVL_INTR_TX_FAULT_SUBLINK_CHANGE)) { nvlink_err("Fatal: TX Fault Sublink Change DL interrupt hit" " on link "); fatal_mask |= BIT(NVL_INTR_TX_FAULT_SUBLINK_CHANGE); inforom_mask |= BIT(DL_TX_FAULT_SUBLINK_CHANGE); } if (intr_status & BIT(NVL_INTR_RX_FAULT_SUBLINK_CHANGE)) { nvlink_err("Fatal: RX Fault Sublink Change DL interrupt hit" " on link"); fatal_mask |= BIT(NVL_INTR_RX_FAULT_SUBLINK_CHANGE); inforom_mask |= BIT(DL_RX_FAULT_SUBLINK_CHANGE); } if (intr_status & BIT(NVL_INTR_RX_FAULT_DL_PROTOCOL)) { nvlink_err("Fatal: RX Fault DL Protocol interrupt hit on link"); fatal_mask |= BIT(NVL_INTR_RX_FAULT_DL_PROTOCOL); inforom_mask |= BIT(DL_RX_FAULT_DL_PROTOCOL); } if (intr_status & BIT(NVL_INTR_RX_SHORT_ERROR_RATE)) { nvlink_err("Non Fatal: RX Short Error Rate DL interrupt hit" " on link "); nonfatal_mask |= BIT(NVL_INTR_RX_SHORT_ERROR_RATE); } if (intr_status & BIT(NVL_INTR_RX_LONG_ERROR_RATE)) { nvlink_err("Non Fatal: RX Long Error Rate Change DL interrupt" " hit on link"); nonfatal_mask |= BIT(NVL_INTR_RX_LONG_ERROR_RATE); } if (intr_status & BIT(NVL_INTR_RX_ILA_TRIGGER)) { nvlink_err("Non Fatal: RX internal Logic Analyzer DL interrupt" " hit on link. Ignore"); nonfatal_mask |= BIT(NVL_INTR_RX_ILA_TRIGGER); } if (intr_status & BIT(NVL_INTR_LTSSM_FAULT)) { nvlink_err("Fatal: LTSSM Fault DL interrupt hit on link "); fatal_mask |= BIT(NVL_INTR_LTSSM_FAULT); inforom_mask |= BIT(DL_LTSSM_FAULT); } if (intr_status & BIT(NVL_INTR_LTSSM_PROTOCOL)) { nvlink_err("Non Fatal: LTSSM Protocol DL interrupt hit on link." " Ignore for now"); nonfatal_mask |= BIT(NVL_INTR_LTSSM_PROTOCOL); } if ((fatal_mask | nonfatal_mask) != 0) { intr_status &= ~(nonfatal_mask | fatal_mask); if (intr_status) { /* did not log all interrupts received */ nvlink_err("Unable to service enabled interrupts for" " link"); ret = -1; } } if (fatal_mask) *retrain_from_safe = false; /* * NOTE: _TX_RECOVERY_LONG is non-fatal if handled by SW, but still * should be logged to inforom here. */ if (fatal_mask | inforom_mask) { err_masks.dl = fatal_mask; nvlink_handle_link_errors(tdev, &err_masks, inforom_mask); } if (*retrain_from_safe) { if (tdev->rm_shim_enabled) { /* * FIXME: Fix the following hack. The proper solution * would be to increment the error recoveries count * after RM notifies us that the link has been * retrained. */ nvlink_dbg("We've encountered an error which requires" " link retraining but we 're in RM shim driver" " mode. And in RM shim driver mode we don't" " know when RM will retrain the link. Currently" " we just assume that RM will retrain the link" " successfully. Therefore, we blindly increment" " the successful error recoveries count."); tdev->tlink.error_recoveries++; } else { if (nvlink_retrain_link(tdev, false)) { nvlink_err("Fatal: Unable to retrain Link from" " SAFE mode"); ret = -1; } } } /* Clear interrupt register (W1C) */ nvlw_nvl_writel(tdev, NVL_INTR, (nonfatal_mask | fatal_mask)); /* Always clear SW2 to cover sideband "err" interfaces to NVLIPT */ nvlw_nvl_writel(tdev, NVL_INTR_SW2, 0xffffffff); return ret; } /* Get status of TL interrupts */ static void nvltlc_get_intr_status(struct tnvlink_dev *tdev, u32 *tlc_tx_err_status0, u32 *tlc_rx_err_status0, u32 *tlc_rx_err_status1) { *tlc_tx_err_status0 = nvlw_nvltlc_readl(tdev, NVLTLC_TX_ERR_STATUS_0); *tlc_rx_err_status0 = nvlw_nvltlc_readl(tdev, NVLTLC_RX_ERR_STATUS_0); *tlc_rx_err_status1 = nvlw_nvltlc_readl(tdev, NVLTLC_RX_ERR_STATUS_1); } static void nvltlc_service_rx0_intr(struct tnvlink_dev *tdev) { u32 intr_status, fatal_mask = 0; u64 inforom_mask = 0; u32 intr_injected_mask = 0; struct nvlink_link_error_masks err_masks = {0}; intr_status = tdev->tlink.tlc_rx_err_status0; if (!intr_status) return; /* TODO: Do the below step only if Error Injection Mode Refcnt state * set to REFCNT_STATE_ENABLED */ intr_injected_mask = nvlw_nvltlc_readl(tdev, NVLTLC_RX_ERR_INJECT_0); if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXDLHDRPARITYERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive DL Header Parity Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXDLHDRPARITYERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXDLHDRPARITYERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_DL_HDR_PARITY); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXDLDATAPARITYERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive DL Data Parity Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXDLDATAPARITYERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXDLDATAPARITYERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_DL_DATA_PARITY); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXDLCTRLPARITYERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive DL Control Parity Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXDLCTRLPARITYERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXDLCTRLPARITYERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_DL_CTRL_PARITY); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXRAMDATAPARITYERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive RAM Data Parity Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXRAMDATAPARITYERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXRAMDATAPARITYERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RAM_DATA_PARITY); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXRAMHDRPARITYERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive RAM Header Parity Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXRAMHDRPARITYERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXRAMHDRPARITYERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RAM_HDR_PARITY); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXINVALIDAEERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Invalid AE Flit Received Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXINVALIDAEERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXINVALIDAEERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_INVALID_AE_FLIT_RCVD); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXINVALIDBEERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Invalid BE Flit Received Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXINVALIDBEERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXINVALIDBEERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_INVALID_BE_FLIT_RCVD); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXINVALIDADDRALIGNERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Invalid Address Alignment Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXINVALIDADDRALIGNERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXINVALIDADDRALIGNERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_INVALID_ADDR_ALIGN); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXPKTLENERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Packet Length Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXPKTLENERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXPKTLENERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_PKT_LEN); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RSVCMDENCERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Reserved Command Encoding Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RSVCMDENCERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RSVCMDENCERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RSVD_CMD_ENC); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RSVDATLENENCERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Reserved Data Length Encoding Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RSVDATLENENCERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RSVDATLENENCERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RSVD_DAT_LEN_ENC); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RSVADDRTYPEERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Reserved Address Type Encoding Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RSVADDRTYPEERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RSVADDRTYPEERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RSVD_ADDR_TYPE); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RSVRSPSTATUSERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Reserved RspStatus Encoding Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RSVRSPSTATUSERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RSVRSPSTATUSERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RSVD_RSP_STATUS); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RSVPKTSTATUSERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Reserved Packet Status Encoding Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RSVPKTSTATUSERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RSVPKTSTATUSERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RSVD_PKT_STATUS); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RSVCACHEATTRPROBEREQERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Reserved Cache Attribute Encoding in Probe" " Request Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RSVCACHEATTRPROBEREQERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RSVCACHEATTRPROBEREQERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RSVD_CACHE_ATTR_ENC_IN_PROBE_REQ); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RSVCACHEATTRPROBERSPERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Reserved Cache Attribute Encoding in Probe" " Response Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RSVCACHEATTRPROBERSPERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RSVCACHEATTRPROBERSPERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RSVD_CACHE_ATTR_ENC_IN_PROBE_RESP); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_DATLENGTATOMICREQMAXERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive DatLen is greater than the Atomic Max size" " (128B, 64B for CAS) Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_DATLENGTATOMICREQMAXERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_DATLENGTATOMICREQMAXERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_DAT_LEN_GT_ATOMIC_REQ_MAX_SIZE); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_DATLENGTRMWREQMAXERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive DatLen is greater than the RMW Max size" " (64B) Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_DATLENGTRMWREQMAXERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_DATLENGTRMWREQMAXERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_DAT_LEN_GT_RMW_REQ_MAX_SIZE); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_DATLENLTATRRSPMINERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive DatLen is less than the ATR response size" " (8B) Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_DATLENLTATRRSPMINERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_DATLENLTATRRSPMINERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_DAT_LEN_LT_ATR_RESP_MIN_SIZE); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_INVALIDCACHEATTRPOERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive The CacheAttr field and PO field do not" " agree Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_INVALIDCACHEATTRPOERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_INVALIDCACHEATTRPOERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_INVALID_PO_FOR_CACHE_ATTR); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_INVALIDCRERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Invalid compressed response Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_INVALIDCRERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_INVALIDCRERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_INVALID_COMPRESSED_RESP); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXRESPSTATUSTARGETERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive TE Error in the RspStatus field"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_0_RXRESPSTATUSTARGETERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_0_RXRESPSTATUSTARGETERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RESP_STATUS_TARGET); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_0_RXRESPSTATUSUNSUPPORTEDREQUESTERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive UR Error in the RspStatus field"); fatal_mask |= BIT( NVLTLC_RX_ERR_STATUS_0_RXRESPSTATUSUNSUPPORTEDREQUESTERR); if (!(intr_injected_mask & BIT( NVLTLC_RX_ERR_INJECT_0_RXRESPSTATUSUNSUPPORTEDREQUESTERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_RESP_STATUS_UNSUPPORTED_REQUEST); } } if (fatal_mask) { /* * Handle fatal errors, which may result in disabling of the * interrupts. */ err_masks.tlc_rx0 = fatal_mask & ~intr_injected_mask; err_masks.tlc_rx0_injected = fatal_mask & intr_injected_mask; nvlink_handle_link_errors(tdev, &err_masks, inforom_mask); /* Clear signaled first and then status bits (W1C) */ nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_FIRST_0, fatal_mask); nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_STATUS_0, fatal_mask); } } /* Service TLC RX 1 interrupts */ static void nvltlc_service_rx1_intr(struct tnvlink_dev *tdev) { u32 intr_status, fatal_mask = 0; u64 inforom_mask = 0; u32 intr_injected_mask = 0; int i; struct nvlink_link_error_masks err_masks = {0}; intr_status = tdev->tlink.tlc_rx_err_status1; if (!intr_status) return; /* TODO: Do the below step only if Error Injection Mode Refcnt state * set to REFCNT_STATE_ENABLED */ intr_injected_mask = nvlw_nvltlc_readl(tdev, NVLTLC_RX_ERR_INJECT_1); for (i = 0; i < 8; i++) { if (NVLTLC_RX_ERR_STATUS_1_RXHDROVFERR_V(intr_status) & BIT(i)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Header Overflow Error (VC%d)", i); fatal_mask |= NVLTLC_RX_ERR_STATUS_1_RXHDROVFERR_F(BIT(i)); if (!(NVLTLC_RX_ERR_INJECT_1_RXHDROVFERR_V( intr_injected_mask) & BIT(i))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_HDR_OVERFLOW); } } if (NVLTLC_RX_ERR_STATUS_1_RXDATAOVFERR_V(intr_status) & BIT(i)) { nvlink_err("Fatal TLC RX interrupt hit on link "); nvlink_err("Receive Data Overflow Error (VC%d)", i); fatal_mask |= NVLTLC_RX_ERR_STATUS_1_RXDATAOVFERR_F(BIT(i)); if (!(NVLTLC_RX_ERR_INJECT_1_RXDATAOVFERR_V( intr_injected_mask) & BIT(i))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_DATA_OVERFLOW); } } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_1_STOMPDETERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Stomped Packet Received Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_1_STOMPDETERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_1_STOMPDETERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_STOMPED_PKT_RCVD); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_1_RXPOISONERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Data Poisoned Packet Received Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_1_RXPOISONERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_1_RXPOISONERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_DATA_POISONED_PKT_RCVD); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_1_CORRECTABLEINTERNALERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Correctable Internal Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_1_CORRECTABLEINTERNALERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_1_CORRECTABLEINTERNALERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_CORRECTABLE_INTERNAL); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_1_RXUNSUPVCOVFERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Unsupported VC Overflow Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_1_RXUNSUPVCOVFERR); if (!(intr_injected_mask & BIT(NVLTLC_RX_ERR_INJECT_1_RXUNSUPVCOVFERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_UNSUPPORTED_VC_OVERFLOW); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_1_RXUNSUPNVLINKCREDITRELERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Unsupported NVLink Credit Release Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_1_RXUNSUPNVLINKCREDITRELERR); if (!(intr_injected_mask & BIT( NVLTLC_RX_ERR_INJECT_1_RXUNSUPNVLINKCREDITRELERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_RX_UNSUPPORTED_NVLINK_CREDIT_RELEASE); } } if (intr_status & BIT(NVLTLC_RX_ERR_STATUS_1_RXUNSUPNCISOCCREDITRELERR)) { nvlink_err("Fatal TLC RX interrupt hit on link"); nvlink_err("Receive Unsupported NCISOC Credit Release Error"); fatal_mask |= BIT(NVLTLC_RX_ERR_STATUS_1_RXUNSUPNCISOCCREDITRELERR); if (!(intr_injected_mask & BIT( NVLTLC_RX_ERR_INJECT_1_RXUNSUPNCISOCCREDITRELERR))) { /* log to inforom if not injected */ inforom_mask |= BIT( TLC_RX_UNSUPPORTED_NCISOC_CREDIT_RELEASE); } } if (fatal_mask != 0) { /* Handle fatal errors, which may result in disabling of the * interrupts */ err_masks.tlc_rx1 = fatal_mask & ~intr_injected_mask; err_masks.tlc_rx1_injected = fatal_mask & intr_injected_mask; nvlink_handle_link_errors(tdev, &err_masks, inforom_mask); /* Clear signaled first and then status bits (W1C) */ nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_FIRST_1, fatal_mask); nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_STATUS_1, fatal_mask); } } static void nvltlc_service_tx_intr(struct tnvlink_dev *tdev) { u32 intr_status, fatal_mask = 0; u64 inforom_mask = 0; u32 intr_injected_mask = 0; int i; struct nvlink_link_error_masks err_masks = {0}; intr_status = tdev->tlink.tlc_tx_err_status0; if (!intr_status) return; /* TODO: Do the below step only if Error Injection Mode Refcnt state * set to REFCNT_STATE_ENABLED */ intr_injected_mask = nvlw_nvltlc_readl(tdev, NVLTLC_TX_ERR_INJECT_0); for (i = 0; i < 8; i++) { if (NVLTLC_TX_ERR_STATUS_0_TXHDRCREDITOVFERR_V(intr_status) & BIT(i)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit Header Credit Overflow Error" " (VC%d)", i); fatal_mask |= NVLTLC_TX_ERR_STATUS_0_TXHDRCREDITOVFERR_F( BIT(i)); if (!(NVLTLC_TX_ERR_INJECT_0_TXHDRCREDITOVFERR_V( intr_injected_mask) & BIT(i))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_HDR_CREDIT_OVERFLOW); } } if (NVLTLC_TX_ERR_STATUS_0_TXDATACREDITOVFERR_V(intr_status) & BIT(i)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit Data Credit Overflow Error" " (VC%d)", i); fatal_mask |= NVLTLC_TX_ERR_STATUS_0_TXDATACREDITOVFERR_F( BIT(i)); if (!(NVLTLC_TX_ERR_INJECT_0_TXDATACREDITOVFERR_V( intr_injected_mask) & BIT(i))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_DATA_CREDIT_OVERFLOW); } } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_TXDLCREDITOVFERR)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit DL Replay Credit Overflow Error"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_TXDLCREDITOVFERR); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_TXDLCREDITOVFERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_DL_REPLAY_CREDIT_OVERFLOW); } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_TXDLCREDITPARITYERR)) { nvlink_err("Fatal TLC TX interrupt hit on link "); nvlink_err("Transmit DL Flow Control Interface Parity Error"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_TXDLCREDITPARITYERR); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_TXDLCREDITPARITYERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_DL_FLOW_CONTROL_PARITY); } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_TXRAMHDRPARITYERR)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit RAM Header Parity Error"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_TXRAMHDRPARITYERR); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_TXRAMHDRPARITYERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_RAM_HDR_PARITY); } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_TXRAMDATAPARITYERR)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit RAM Data Parity Error"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_TXRAMDATAPARITYERR); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_TXRAMDATAPARITYERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_RAM_DATA_PARITY); } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_TXUNSUPVCOVFERR)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit Unsupported VC Overflow Error"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_TXUNSUPVCOVFERR); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_TXUNSUPVCOVFERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_UNSUPPORTED_VC_OVERFLOW); } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_TXSTOMPDET)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit Stomped Packet Detected on Transmit from" " NCISOC to NVLink"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_TXSTOMPDET); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_TXSTOMPDET))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_STOMPED_PKT_SENT); } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_TXPOISONDET)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit Data Poisoned Packet detected on transmit" " from NCISOC to NVLink"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_TXPOISONDET); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_TXPOISONDET))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_DATA_POISONED_PKT_SENT); } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_TARGETERR)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit Target Error detected in RspStatus"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_TARGETERR); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_TARGETERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_RESP_STATUS_TARGET); } } if (intr_status & BIT(NVLTLC_TX_ERR_STATUS_0_UNSUPPORTEDREQUESTERR)) { nvlink_err("Fatal TLC TX interrupt hit on link"); nvlink_err("Transmit Unsupported Request detected in" " RspStatus"); fatal_mask |= BIT(NVLTLC_TX_ERR_STATUS_0_UNSUPPORTEDREQUESTERR); if (!(intr_injected_mask & BIT(NVLTLC_TX_ERR_INJECT_0_UNSUPPORTEDREQUESTERR))) { /* log to inforom if not injected */ inforom_mask |= BIT(TLC_TX_RESP_STATUS_UNSUPPORTED_REQUEST); } } if (fatal_mask != 0) { /* * Handle fatal errors, which may result in disabling of the * interrupts. */ err_masks.tlc_tx = fatal_mask & ~intr_injected_mask; err_masks.tlc_tx_injected = fatal_mask & intr_injected_mask; nvlink_handle_link_errors(tdev, &err_masks, inforom_mask); // Clear signaled first and then status bits (W1C) nvlw_nvltlc_writel(tdev, NVLTLC_TX_ERR_FIRST_0, fatal_mask); nvlw_nvltlc_writel(tdev, NVLTLC_TX_ERR_STATUS_0, fatal_mask); } } /* Service NVLIPT interrupts */ static void nvlink_service_nvlipt_interrupts(struct tnvlink_dev *tdev) { u32 nvlipt_err_uc_active_bits = 0; nvlipt_err_uc_active_bits = BIT(NVLIPT_ERR_UC_STATUS_LINK0_DLPROTOCOL) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_DATAPOISONED) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_FLOWCONTROL) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_RESPONSETIMEOUT) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_TARGETERROR) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_UNEXPECTEDRESPONSE) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_RECEIVEROVERFLOW) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_MALFORMEDPACKET) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_STOMPEDPACKETRECEIVED) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_UNSUPPORTEDREQUEST) | BIT(NVLIPT_ERR_UC_STATUS_LINK0_UCINTERNAL); /* * Interrupt handling (mask/handle/unmask) happens in the leaf handlers, * here we simply assume all interrupts were handled and clear the * roll ups. */ nvlw_nvlipt_writel(tdev, NVLIPT_ERR_UC_FIRST_LINK0, nvlipt_err_uc_active_bits); nvlw_nvlipt_writel(tdev, NVLIPT_ERR_UC_STATUS_LINK0, nvlipt_err_uc_active_bits); } static int nvlink_service_tlc_interrupts(struct tnvlink_dev *tdev) { /* TLC RX interrupts (0) */ nvltlc_service_rx0_intr(tdev); /* TLC RX interrupts (1) */ nvltlc_service_rx1_intr(tdev); /* TLC TX interrupts */ nvltlc_service_tx_intr(tdev); return 0; } static u32 nvlink_service_link(struct tnvlink_dev *tdev) { u32 tlc_tx_err_status0; u32 tlc_rx_err_status0; u32 tlc_rx_err_status1; bool retrain_from_safe = false; /* * Cache the error log register for clients. Need to cache it here * because if the link is retrained during the DL interrupt handler * it will clear the TL interrupt status. */ nvltlc_get_intr_status(tdev, &tlc_tx_err_status0, &tlc_rx_err_status0, &tlc_rx_err_status1); tdev->tlink.tlc_tx_err_status0 |= tlc_tx_err_status0; tdev->tlink.tlc_rx_err_status0 |= tlc_rx_err_status0; tdev->tlink.tlc_rx_err_status1 |= tlc_rx_err_status1; nvlink_service_dl_interrupts(tdev, &retrain_from_safe); nvlink_service_tlc_interrupts(tdev); /* NVLIPT is the IP top level, it goes last */ nvlink_service_nvlipt_interrupts(tdev); return 0; } /* Disable link interrupts */ void nvlink_disable_link_interrupts(struct tnvlink_dev *tdev) { minion_disable_link_intr(tdev); nvlink_disable_tl_interrupts(tdev); nvlink_disable_dl_interrupts(tdev); nvlink_disable_nvlipt_interrupts(tdev); } irqreturn_t t19x_nvlink_endpt_isr(int irq, void *dev_id) { struct tnvlink_dev *tdev = dev_id; nvlink_dbg("Interrupt received! IRQ # = %d", irq); /* Service MINION first (per arch) */ nvlink_minion_service_intr(tdev); nvlink_service_link(tdev); return IRQ_HANDLED; }