/* * SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "nvlink_export.h" #include "common_nvswitch.h" #include "ls10/ls10.h" #include "ls10/minion_ls10.h" #include "ls10/minion_nvlink_defines_public_ls10.h" #include "regkey_nvswitch.h" #include "nvswitch/ls10/dev_minion_ip.h" #include "nvswitch/ls10/dev_minion_ip_addendum.h" #include "nvswitch/ls10/dev_ingress_ip.h" #include "nvswitch/ls10/dev_egress_ip.h" #include "nvswitch/ls10/dev_riscv_pri.h" #include "nvswitch/ls10/dev_nvlphyctl_ip.h" #include "flcn/flcn_nvswitch.h" /* * @Brief : Check if MINION is already running. * * The function assumes that if one of MINIONs is running, all of them are * running. This approach needs to be fixed. * * TODO: Refactor minion code to check for each minion's status individually. * * @param[in] device Bootstrap MINIONs on this device */ static NvBool _nvswitch_check_running_minions ( nvswitch_device *device ) { NvU32 data, i; NvBool bMinionRunning = NV_FALSE; for (i = 0; i < NUM_MINION_ENGINE_LS10; i++) { if (!NVSWITCH_ENG_VALID_LS10(device, MINION, i)) { NVSWITCH_PRINT(device, SETUP, "%s: MINION instance %d is not valid.\n", __FUNCTION__, i); continue; } data = NVSWITCH_MINION_RD32_LS10(device, i, _CMINION, _FALCON_IRQSTAT); if (FLD_TEST_DRF(_CMINION, _FALCON_IRQSTAT, _HALT, _FALSE, data)) { data = NVSWITCH_MINION_RD32_LS10(device, i, _MINION, _MINION_STATUS); if (FLD_TEST_DRF(_MINION, _MINION_STATUS, _STATUS, _BOOT, data)) { // // Set initialized flag if MINION is running. // We don't want to bootstrap a falcon that is already running. // nvswitch_set_minion_initialized(device, i, NV_TRUE); NVSWITCH_PRINT(device, SETUP, "%s: MINION instance %d is already bootstrapped.\n", __FUNCTION__, i); bMinionRunning = NV_TRUE; } } } return bMinionRunning; } NvlStatus nvswitch_minion_get_dl_status_ls10 ( nvswitch_device *device, NvU32 linkId, NvU32 statusIdx, NvU32 statusArgs, NvU32 *statusData ) { NVSWITCH_TIMEOUT timeout; NvBool keepPolling; NvU32 regData, localLinkNumber; localLinkNumber = linkId % NVSWITCH_LINKS_PER_MINION_LS10; if (NVSWITCH_IS_LINK_ENG_VALID_LS10(device, MINION, linkId) && !nvswitch_is_minion_initialized(device, NVSWITCH_GET_LINK_ENG_INST(device, linkId, MINION))) { NVSWITCH_PRINT(device, ERROR, "%s: MINION %d is not initialized for link %08x.\n", __FUNCTION__, NVSWITCH_GET_LINK_ENG_INST(device, linkId, MINION), linkId); return -NVL_ERR_INVALID_STATE; } // Query the DL status interface to get the data NVSWITCH_MINION_LINK_WR32_LS10(device, linkId, _MINION, _NVLINK_DL_STAT(localLinkNumber), DRF_NUM(_MINION, _NVLINK_DL_STAT, _ARGS, statusArgs) | DRF_NUM(_MINION, _NVLINK_DL_STAT, _STATUSIDX, statusIdx)); if (IS_FMODEL(device) || IS_EMULATION(device) || IS_RTLSIM(device)) { nvswitch_timeout_create(20 * NVSWITCH_INTERVAL_1SEC_IN_NS, &timeout); } else { nvswitch_timeout_create(NVSWITCH_INTERVAL_5MSEC_IN_NS, &timeout); } // Poll for READY bit to be set do { keepPolling = (nvswitch_timeout_check(&timeout)) ? NV_FALSE : NV_TRUE; regData = NVSWITCH_MINION_LINK_RD32_LS10(device, linkId, _MINION, _NVLINK_DL_STAT(localLinkNumber)); if (FLD_TEST_DRF_NUM(_MINION, _NVLINK_DL_STAT, _READY, 1, regData)) { *statusData = NVSWITCH_MINION_LINK_RD32_LS10(device, linkId, _MINION, _NVLINK_DL_STATDATA(localLinkNumber)); return NVL_SUCCESS; } if (IS_FMODEL(device) || IS_RTLSIM(device)) { nvswitch_os_sleep(1); } } while (keepPolling); NVSWITCH_PRINT(device, ERROR, "%s: Timeout waiting for DL_STAT request to complete" " NV_MINION_NVLINK_DL_STAT(%d) = 0x%08x\n", __FUNCTION__, linkId, regData); return -NVL_ERR_INVALID_STATE; } /* * @Brief : Send MINION DL CMD for a particular link * * @param[in] device Send command to MINION on this device * @param[in] linkNumber DLCMD will be sent on this link number * * @return Returns true if the DLCMD passed */ NvlStatus nvswitch_minion_send_command_ls10 ( nvswitch_device *device, NvU32 linkNumber, NvU32 command, NvU32 scratch0 ) { NvU32 data = 0, localLinkNumber, statData = 0; NvU32 ingressEccRegVal = 0, egressEccRegVal = 0; NVSWITCH_TIMEOUT timeout; NvBool keepPolling; localLinkNumber = linkNumber % NVSWITCH_LINKS_PER_MINION_LS10; if (NVSWITCH_IS_LINK_ENG_VALID_LS10(device, MINION, linkNumber) && !nvswitch_is_minion_initialized(device, NVSWITCH_GET_LINK_ENG_INST(device, linkNumber, MINION))) { NVSWITCH_PRINT(device, ERROR, "%s: MINION %d is not initialized for link %08x.\n", __FUNCTION__, NVSWITCH_GET_LINK_ENG_INST(device, linkNumber, MINION), linkNumber); return NVL_SUCCESS; } data = NVSWITCH_MINION_LINK_RD32_LS10(device, linkNumber, _MINION, _NVLINK_DL_CMD(localLinkNumber)); if (FLD_TEST_DRF_NUM(_MINION, _NVLINK_DL_CMD, _FAULT, 1, data)) { NVSWITCH_PRINT(device, ERROR, "%s: MINION %d is in fault state. NV_MINION_NVLINK_DL_CMD(%d) = %08x\n", __FUNCTION__, NVSWITCH_GET_LINK_ENG_INST(device, linkNumber, MINION), linkNumber, data); return -NVL_ERR_GENERIC; } // Write to minion scratch if needed by command switch (command) { case NV_MINION_NVLINK_DL_CMD_COMMAND_CONFIGEOM: data = 0; data = FLD_SET_DRF_NUM(_MINION, _MISC_0, _SCRATCH_SWRW_0, scratch0, data); NVSWITCH_MINION_WR32_LS10(device, NVSWITCH_GET_LINK_ENG_INST(device, linkNumber, MINION), _MINION, _MISC_0, data); break; case NV_MINION_NVLINK_DL_CMD_COMMAND_INITPHASE1: // // WAR bug 2708497 // Before INITPHASE1, we must clear these values, then set back to // _PROD after the call // NV_INGRESS_ERR_ECC_CTRL_NCISOC_PARITY_ENABLE // NV_EGRESS_ERR_ECC_CTRL_NCISOC_PARITY_ENABLE // ingressEccRegVal = NVSWITCH_NPORT_RD32_LS10(device, linkNumber, _INGRESS, _ERR_ECC_CTRL); NVSWITCH_NPORT_WR32_LS10(device, linkNumber, _INGRESS, _ERR_ECC_CTRL, FLD_SET_DRF(_INGRESS, _ERR_ECC_CTRL, _NCISOC_PARITY_ENABLE, _DISABLE, ingressEccRegVal)); egressEccRegVal = NVSWITCH_NPORT_RD32_LS10(device, linkNumber, _EGRESS, _ERR_ECC_CTRL); NVSWITCH_NPORT_WR32_LS10(device, linkNumber, _EGRESS, _ERR_ECC_CTRL, FLD_SET_DRF(_EGRESS, _ERR_ECC_CTRL, _NCISOC_PARITY_ENABLE, _DISABLE, egressEccRegVal)); break; default: break; } data = FLD_SET_DRF_NUM(_MINION, _NVLINK_DL_CMD, _COMMAND, command, data); data = FLD_SET_DRF_NUM(_MINION, _NVLINK_DL_CMD, _FAULT, 1, data); NVSWITCH_MINION_LINK_WR32_LS10(device, linkNumber, _MINION, _NVLINK_DL_CMD(localLinkNumber), data); if (IS_FMODEL(device) || IS_EMULATION(device) || IS_RTLSIM(device)) { nvswitch_timeout_create(10 * NVSWITCH_INTERVAL_1SEC_IN_NS, &timeout); } else { nvswitch_timeout_create(NVSWITCH_INTERVAL_5MSEC_IN_NS, &timeout); } // // We will exit this if the command is successful OR // if timeout waiting for the READY bit to be set OR // if it generates a MINION FAULT // do { keepPolling = (nvswitch_timeout_check(&timeout)) ? NV_FALSE : NV_TRUE; data = NVSWITCH_MINION_LINK_RD32_LS10(device, linkNumber, _MINION, _NVLINK_DL_CMD(localLinkNumber)); if (FLD_TEST_DRF_NUM(_MINION, _NVLINK_DL_CMD, _READY, 1, data)) { // The command has completed, success? if (FLD_TEST_DRF_NUM(_MINION, _NVLINK_DL_CMD, _FAULT, 1, data)) { NVSWITCH_PRINT(device, ERROR, "%s: NVLink MINION command faulted!" " NV_MINION_NVLINK_DL_CMD(%d) = 0x%08x\n", __FUNCTION__, linkNumber, data); // Pull fault code and subcode if (nvswitch_minion_get_dl_status(device, linkNumber, NV_NVLSTAT_MN00, 0, &statData) == NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "%s: Minion DLCMD Fault code = 0x%x, Sub-code = 0x%x\n", __FUNCTION__, DRF_VAL(_NVLSTAT, _MN00, _LINK_INTR_CODE, statData), DRF_VAL(_NVLSTAT, _MN00, _LINK_INTR_SUBCODE, statData)); } else { NVSWITCH_PRINT(device, ERROR, "%s: Failed to get code and subcode from DLSTAT, link %d\n", __FUNCTION__, linkNumber); } // Clear the fault and return NVSWITCH_PRINT(device, ERROR, "%s: Clearing NVLink MINION fault for link %d\n", __FUNCTION__, linkNumber); data = FLD_SET_DRF_NUM(_MINION, _NVLINK_DL_CMD, _FAULT, 1, 0x0); NVSWITCH_MINION_LINK_WR32_LS10(device, linkNumber, _MINION, _NVLINK_DL_CMD(localLinkNumber), data); return (DRF_VAL(_NVLSTAT, _MN00, _LINK_INTR_SUBCODE, statData) == MINION_ALARM_BUSY) ? -NVL_ERR_STATE_IN_USE : -NVL_ERR_INVALID_STATE; } else { NVSWITCH_PRINT(device, SETUP, "%s: NVLink MINION command %x was sent successfully for link %d\n", __FUNCTION__, command, linkNumber); break; } } nvswitch_os_sleep(1); } while (keepPolling); if (!FLD_TEST_DRF_NUM(_MINION, _NVLINK_DL_CMD, _READY, 1, data)) { NVSWITCH_PRINT(device, ERROR, "%s: Timeout waiting for NVLink MINION command to complete!" " NV_MINION_NVLINK_DL_CMD(%d) = 0x%08x\n", __FUNCTION__, linkNumber, data); return -NVL_ERR_INVALID_STATE; } if (command == NV_MINION_NVLINK_DL_CMD_COMMAND_INITPHASE1) { NVSWITCH_NPORT_WR32_LS10(device, linkNumber, _INGRESS, _ERR_ECC_CTRL, ingressEccRegVal); NVSWITCH_NPORT_WR32_LS10(device, linkNumber, _EGRESS, _ERR_ECC_CTRL, egressEccRegVal); } return NVL_SUCCESS; } /* * @Brief : Bootstrap all MINIONs on the specified device * * @param[in] device Bootstrap MINIONs on this device */ NvlStatus nvswitch_init_minion_ls10 ( nvswitch_device *device ) { NvlStatus status = NVL_SUCCESS; if (_nvswitch_check_running_minions(device)) { return NVL_SUCCESS; } status = -NVL_INITIALIZATION_TOTAL_FAILURE; return status; } NvlStatus nvswitch_set_minion_initialized_ls10 ( nvswitch_device *device, NvU32 idx_minion, NvBool initialized ) { ls10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LS10(device); if (!NVSWITCH_ENG_VALID_LS10(device, MINION, idx_minion)) { return -NVL_BAD_ARGS; } chip_device->engMINION[idx_minion].initialized = initialized; return NVL_SUCCESS; } NvBool nvswitch_is_minion_initialized_ls10 ( nvswitch_device *device, NvU32 idx_minion ) { ls10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LS10(device); if (!NVSWITCH_ENG_VALID_LS10(device, MINION, idx_minion)) { return NV_FALSE; } return (chip_device->engMINION[idx_minion].initialized != 0); } NvlStatus nvswitch_minion_set_sim_mode_ls10 ( nvswitch_device *device, nvlink_link *link ) { NvlStatus status = NVL_SUCCESS; NvU32 dlcmd; NvU32 linkNumber = link->linkNumber; NvU32 localLinkNumber = linkNumber % NVSWITCH_LINKS_PER_MINION_LS10; switch (device->regkeys.set_simmode) { case NV_SWITCH_REGKEY_MINION_SET_SIMMODE_FAST: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_FAST; break; case NV_SWITCH_REGKEY_MINION_SET_SIMMODE_MEDIUM: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_MEDIUM; break; case NV_SWITCH_REGKEY_MINION_SET_SIMMODE_SLOW: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_SLOW; break; default: return NVL_SUCCESS; } status = nvswitch_minion_send_command(device, linkNumber, dlcmd, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "%s: DLCMD 0x%x failed on link: %d\n", __FUNCTION__, dlcmd, linkNumber); return status; } // Setting RXCAL_EN_ALARM timer value NVSWITCH_MINION_LINK_WR32_LS10(device, linkNumber, _MINION, _NVLINK_DL_CMD_DATA(localLinkNumber), NV_MINION_DL_CMD_DATA_RXCAL_EN_ALARM); status = nvswitch_minion_send_command(device, linkNumber, NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_RXCAL_EN_ALARM, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "%s: DLCMD DBG_SETSIMMODE_RXCAL_EN_ALARM failed on link: %d\n", __FUNCTION__, linkNumber); return status; } // Setting INIT_CAL_DONE timer value NVSWITCH_MINION_LINK_WR32_LS10(device, linkNumber, _MINION, _NVLINK_DL_CMD_DATA(localLinkNumber), NV_MINION_DL_CMD_DATA_INIT_CAL_DONE); status = nvswitch_minion_send_command(device, linkNumber, NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_INIT_CAL_DONE, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "%s: DLCMD DBG_SETSIMMODE_INIT_CAL_DONE failed on link: %d\n", __FUNCTION__, linkNumber); return status; } return status; } NvlStatus nvswitch_minion_set_smf_settings_ls10 ( nvswitch_device *device, nvlink_link *link ) { NvlStatus status = NVL_SUCCESS; NvU32 dlcmd; NvU32 linkNumber = link->linkNumber; switch (device->regkeys.set_smf_settings) { case NV_SWITCH_REGKEY_MINION_SET_SMF_SETTINGS_SLOW: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_SMF_VALUES_SLOW; break; case NV_SWITCH_REGKEY_MINION_SET_SMF_SETTINGS_MEDIUM: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_SMF_VALUES_MEDIUM; break; case NV_SWITCH_REGKEY_MINION_SET_SMF_SETTINGS_FAST: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_SMF_VALUES_FAST; break; case NV_SWITCH_REGKEY_MINION_SET_SMF_SETTINGS_MEDIUM_SERIAL: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_SMF_VALUES_MEDIUM_SERIAL; break; default: return NVL_SUCCESS; } status = nvswitch_minion_send_command(device, linkNumber, dlcmd, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "%s: DLCMD 0x%x failed on link: %d\n", __FUNCTION__, dlcmd, linkNumber); return status; } return status; } NvlStatus nvswitch_minion_select_uphy_tables_ls10 ( nvswitch_device *device, nvlink_link *link ) { NvlStatus status = NVL_SUCCESS; NvU32 dlcmd; NvU32 linkNumber = link->linkNumber; switch (device->regkeys.select_uphy_tables) { case NV_SWITCH_REGKEY_MINION_SELECT_UPHY_TABLES_SHORT: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_UPHY_TABLES_SHORT; break; case NV_SWITCH_REGKEY_MINION_SELECT_UPHY_TABLES_FAST: dlcmd = NV_MINION_NVLINK_DL_CMD_COMMAND_DBG_SETSIMMODE_UPHY_TABLES_FAST; break; default: return NVL_SUCCESS; } status = nvswitch_minion_send_command(device, linkNumber, dlcmd, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "%s: DLCMD 0x%x failed on link: %d\n", __FUNCTION__, dlcmd, linkNumber); return status; } return status; } NvlStatus nvswitch_minion_get_rxdet_status_ls10 ( nvswitch_device *device, NvU32 linkId ) { NvU32 statData; NvlStatus status; NVSWITCH_TIMEOUT timeout; NvBool keepPolling; if (IS_FMODEL(device) || IS_EMULATION(device) || IS_RTLSIM(device)) { nvswitch_timeout_create(30*NVSWITCH_INTERVAL_1SEC_IN_NS, &timeout); } else { nvswitch_timeout_create(20 * NVSWITCH_INTERVAL_1MSEC_IN_NS, &timeout); } // Poll for READY bit to be set do { keepPolling = (nvswitch_timeout_check(&timeout)) ? NV_FALSE : NV_TRUE; // Check RXDET status on MINION DL STAT interface status = nvswitch_minion_get_dl_status(device, linkId, NV_NVLSTAT_LNK2, 0, &statData); if (status != NVL_SUCCESS) { return status; } if (FLD_TEST_DRF(_NVLSTAT, _LNK2, _RXDET_LINK_STATUS, _FOUND, statData)) { NVSWITCH_PRINT(device, INFO, "%s: RXDET LINK_STATUS = FOUND on link: %d\n", __FUNCTION__, linkId); // Retrieve which lanes were found (should be all) device->link[linkId].lane_rxdet_status_mask = DRF_VAL(_NVLSTAT, _LNK2, _RXDET_LANE_STATUS, statData); // // MINION doesn't have knowledge of lane reversal and therefore // reports logical lanes. We must reverse the bitmask here if applicable // since RM reports physical lanes. // if (nvswitch_link_lane_reversed_lr10(device, linkId)) { NVSWITCH_REVERSE_BITMASK_32(NVSWITCH_NUM_LANES_LS10, device->link[linkId].lane_rxdet_status_mask); } return NVL_SUCCESS; } if (FLD_TEST_DRF(_NVLSTAT, _LNK2, _RXDET_LINK_STATUS, _TIMEOUT, statData)) { NVSWITCH_PRINT(device, ERROR, "%s: RXDET LINK_STATUS = TIMEOUT on link: %d\n", __FUNCTION__, linkId); // Retrieve which lanes were found device->link[linkId].lane_rxdet_status_mask = DRF_VAL(_NVLSTAT, _LNK2, _RXDET_LANE_STATUS, statData); // // MINION doesn't have knowledge of lane reversal and therefore // reports logical lanes. We must reverse the bitmask here if applicable // since RM reports physical lanes. // if (nvswitch_link_lane_reversed_lr10(device, linkId)) { NVSWITCH_REVERSE_BITMASK_32(NVSWITCH_NUM_LANES_LS10, device->link[linkId].lane_rxdet_status_mask); } return -NVL_ERR_INVALID_STATE; } nvswitch_os_sleep(1); } while (keepPolling); NVSWITCH_PRINT(device, ERROR, "%s: Timeout waiting for RXDET STATUS on link: %d\n", __FUNCTION__, linkId); return -NVL_ERR_INVALID_STATE; } /* * @Brief : Check if the RISCV CPU has started * * @param[in] device The Nvswitch device * @param[in] idx_minion MINION instance to use */ NvBool nvswitch_minion_is_riscv_active_ls10 ( nvswitch_device *device, NvU32 idx_minion ) { NvU32 val; val = NVSWITCH_MINION_RD32_LS10(device, idx_minion, _CMINION_RISCV, _CPUCTL); return FLD_TEST_DRF(_CMINION, _RISCV_CPUCTL, _ACTIVE_STAT, _ACTIVE, val); } NvlStatus nvswitch_minion_clear_dl_error_counters_ls10 ( nvswitch_device *device, NvU32 linkId ) { NvlStatus status; status = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_DLSTAT_CLR_DLERRCNT, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "%s : Failed to clear error count to MINION for link # %d\n", __FUNCTION__, linkId); } return status; } NvlStatus nvswitch_minion_send_inband_data_ls10 ( nvswitch_device *device, NvU32 linkId, nvswitch_inband_send_data *inBandData ) { NvlStatus status = NVL_SUCCESS; #if defined(INCLUDE_NVLINK_LIB) NvlStatus tempStatus = NVL_SUCCESS; NvU32 localLinkNumber = linkId % NVSWITCH_LINKS_PER_MINION_LS10; NvU8 *sendBuffer = inBandData->sendBuffer; NvU32 bufferSize = inBandData->bufferSize; NvU32 data = 0; NvU32 regval = 0; NvU32 statData = 0; NVSWITCH_TIMEOUT timeout; NvBool bKeepPolling = NV_TRUE; if (bufferSize == 0 || bufferSize > NVLINK_INBAND_MAX_XFER_SIZE) { NVSWITCH_PRINT(device, ERROR, "Bad Inband data size %d. Skipping Inband Send\n", bufferSize); return -NVL_ERR_INVALID_STATE; } // Buffer Size must be reduced by 1 as per the minion protocol regval = DRF_NUM(_MINION, _INBAND_SEND_DATA, _BUFFER_SIZE, (bufferSize - 1)); regval |= DRF_NUM(_MINION, _INBAND_SEND_DATA, _FLAGS,inBandData->hdr.data); NVSWITCH_MINION_WR32_LS10(device, NVSWITCH_GET_LINK_ENG_INST(device, linkId, MINION), _MINION, _NVLINK_DL_CMD_DATA(localLinkNumber), regval); status = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_WRITE_TX_BUFFER_START, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "Link %d Inband Buffer transfer for TX_BUFFER_START failed\n", linkId); return status; } while (bufferSize != 0) { nvswitch_os_memcpy(&data, sendBuffer, NV_MIN(sizeof(data), bufferSize)); NVSWITCH_MINION_WR32_LS10(device, NVSWITCH_GET_LINK_ENG_INST(device, linkId, MINION), _MINION, _NVLINK_DL_CMD_DATA(localLinkNumber), data); status = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_WRITE_TX_BUFFER_MIDDLE, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "Link %d Inband Buffer transfer failed\n", linkId); goto clear_buffer; } bufferSize -= NV_MIN(sizeof(data), bufferSize); sendBuffer += NV_MIN(sizeof(data), bufferSize); } status = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_WRITE_TX_BUFFER_END, 0); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "Link %d Inband Buffer transfer for TX_BUFFER_END\n", linkId); goto clear_buffer; } // Wait for buffer complete or buffer fail nvswitch_timeout_create(2 * NVSWITCH_INTERVAL_4SEC_IN_NS, &timeout); do { bKeepPolling = !nvswitch_timeout_check(&timeout); // DLSTAT need to explicitly checked status = nvswitch_minion_get_dl_status(device, linkId, NV_NVLSTAT_UC01, 0, &statData); if (status != NVL_SUCCESS) { NVSWITCH_PRINT(device, INFO,"%s : Failed to poll DLSTAT register for (%s):(%d)\n", __FUNCTION__, device->name, linkId); status = -NVL_ERR_INVALID_STATE; goto clear_buffer; } if (FLD_TEST_DRF(_NVLSTAT, _UC01, _INBAND_BUFFER_COMPLETE, _TRUE, statData)) { return NVL_SUCCESS; } if (FLD_TEST_DRF(_NVLSTAT, _UC01, _INBAND_BUFFER_FAIL, _TRUE, statData)) { NVSWITCH_PRINT(device, ERROR, "Link %d Inband Buffer transfer BUFFER_FAILED\n", linkId); status = -NVL_ERR_INVALID_STATE; goto clear_buffer; } // // Consider a case where both FM and RM trying to write on the same NVLink at the same time. // Both are waiting on each other to read the buffer, but they have also blocked the ISR, // as while waiting they hold the device lock. Thus, it is necessary to unblock one of them. // And to do that we have service BUFFER_AVAILABLE while waiting. // nvswitch_service_minion_all_links_ls10(device); nvswitch_os_sleep(10); } while (bKeepPolling); NVSWITCH_PRINT(device, ERROR, "Link %d Inband Neither got BUFFER_FAIL nor BUFFER_COMPLETE\n", linkId); clear_buffer: tempStatus = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_CLEAR_TX_BUFFER, 0); if (tempStatus != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "Link %d Inband Buffer transfer for TX_BUFFER_CLEAR\n", linkId); return status; } // Check if we received BUFFER_COMPLETE is seen while doing a BUFFER_CLEAR tempStatus = nvswitch_minion_get_dl_status(device, linkId, NV_NVLSTAT_UC01, 0, &statData); if (tempStatus != NVL_SUCCESS) { NVSWITCH_PRINT(device, INFO,"%s : Failed to poll DLSTAT register for (%s):(%d)\n", __FUNCTION__, device->name, linkId); return status; } if (FLD_TEST_DRF(_NVLSTAT, _UC01, _INBAND_BUFFER_COMPLETE, _TRUE, statData)) { status = NVL_SUCCESS; } else { status = bKeepPolling ? status: -NVL_ERR_STATE_IN_USE; } #endif return status; } void nvswitch_minion_receive_inband_data_ls10 ( nvswitch_device *device, NvU32 linkId ) { NvlStatus status = NVL_SUCCESS; #if defined(INCLUDE_NVLINK_LIB) NvlStatus tempStatus = NVL_SUCCESS; NvU32 numEntries = 0; NvU32 i; NvU32 localLinkNumber = linkId % NVSWITCH_LINKS_PER_MINION_LS10; NvU8 *receiveBuffer; NvU32 regVal, dataSize, remainingBuffer, bytesToXfer; nvlink_inband_drv_hdr_t hdr; status = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_READ_RX_BUFFER_START, 0); if (status != NV_OK) goto cleanup; regVal = NVSWITCH_MINION_RD32_LS10(device, NVSWITCH_GET_LINK_ENG_INST(device, linkId, MINION), _MINION, _NVLINK_DL_CMD_DATA(localLinkNumber)); // Add 1 to the data as per minion protocol dataSize = DRF_VAL(_MINION, _INBAND_SEND_DATA, _BUFFER_SIZE, regVal) + 1; hdr.data = DRF_VAL(_MINION, _INBAND_SEND_DATA, _FLAGS, regVal); numEntries = NV_ALIGN_UP(dataSize, NVLINK_INBAND_MAX_XFER_AT_ONCE)/ NVLINK_INBAND_MAX_XFER_AT_ONCE; remainingBuffer = dataSize; if ((hdr.data & (NVLINK_INBAND_DRV_HDR_TYPE_START | NVLINK_INBAND_DRV_HDR_TYPE_MID | NVLINK_INBAND_DRV_HDR_TYPE_END)) == 0) { NVSWITCH_PRINT(device, ERROR, "InBand: HDR is wrong\n"); goto cleanup; } if (hdr.data & NVLINK_INBAND_DRV_HDR_TYPE_START) { if (device->link[linkId].inbandData.message != NULL) { NVSWITCH_PRINT(device, ERROR, "InBand: Got TYPE_START for existing data\n"); NVSWITCH_ASSERT(0); goto cleanup; } device->link[linkId].inbandData.message = nvswitch_os_malloc(sizeof(nvswitch_inband_data_list)); if (device->link[linkId].inbandData.message == NULL) { status = -NVL_NO_MEM; goto cleanup; } device->link[linkId].inbandData.message->dataSize = 0; } if (device->link[linkId].inbandData.message == NULL) { NVSWITCH_PRINT(device, ERROR, "InBand: Data being sent without _START\n"); goto cleanup; } receiveBuffer = device->link[linkId].inbandData.message->data; receiveBuffer += device->link[linkId].inbandData.message->dataSize; if (((dataSize + device->link[linkId].inbandData.message->dataSize) > NVSWITCH_INBAND_DATA_SIZE) || (dataSize > NVLINK_INBAND_MAX_XFER_SIZE) || (dataSize == 0)) { NVSWITCH_PRINT(device, ERROR, "InBand: Msg is of wrong Size :DataSize = %d Msg Size= %d\n", dataSize, device->link[linkId].inbandData.message->dataSize); NVSWITCH_ASSERT(0); goto cleanup; } for (i = 0; i < numEntries; i++) { status = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_READ_RX_BUFFER_MIDDLE, 0); if (status != NV_OK) { NVSWITCH_PRINT(device, ERROR, "Link %d Inband Buffer receive" "for entry %d failed\n", linkId, i); goto cleanup; } regVal = NVSWITCH_MINION_RD32_LS10(device, NVSWITCH_GET_LINK_ENG_INST(device, linkId, MINION), _MINION, _NVLINK_DL_CMD_DATA(localLinkNumber)); bytesToXfer = NV_MIN(remainingBuffer, NVLINK_INBAND_MAX_XFER_AT_ONCE); nvswitch_os_memcpy(receiveBuffer, ®Val, bytesToXfer); receiveBuffer += bytesToXfer; remainingBuffer -= bytesToXfer; } status = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_READ_RX_BUFFER_END, 0); if (status != NV_OK) { NVSWITCH_PRINT(device, ERROR, "Link %d Inband Buffer receive" "for entry %d failed\n", linkId, numEntries); goto cleanup; } device->link[linkId].inbandData.message->dataSize += dataSize; if (hdr.data & NVLINK_INBAND_DRV_HDR_TYPE_END) { nvswitch_filter_messages(device, linkId); } return; cleanup: tempStatus = nvswitch_minion_send_command(device, linkId, NV_MINION_NVLINK_DL_CMD_COMMAND_CLEAR_RX_BUFFER, 0); if (tempStatus != NVL_SUCCESS) { NVSWITCH_PRINT(device, ERROR, "Link %d Inband Buffer transfer for RX_BUFFER_CLEAR\n", linkId); return; } if (device->link[linkId].inbandData.message != NULL) { nvswitch_os_free(device->link[linkId].inbandData.message); device->link[linkId].inbandData.message = NULL; } //TODO: Check if we need to send a failure msg to client? #endif } NvlStatus nvswitch_minion_get_ali_debug_registers_ls10 ( nvswitch_device *device, nvlink_link *link, NVSWITCH_MINION_ALI_DEBUG_REGISTERS *params ) { NvU32 localLinkNumber = link->linkNumber % NVSWITCH_LINKS_PER_MINION_LS10; if (nvswitch_minion_get_dl_status(device, link->linkNumber, NV_NVLSTAT_MN00, 0, &(params->dlstatMn00)) != NVL_SUCCESS) { NVSWITCH_PRINT(device, INFO,"%s : Failed to poll DLSTAT _MN00 register for (%s):(%d)\n", __FUNCTION__, device->name, link->linkNumber); } if (nvswitch_minion_get_dl_status(device, link->linkNumber, NV_NVLSTAT_UC01, 0, &(params->dlstatUc01)) != NVL_SUCCESS) { NVSWITCH_PRINT(device, INFO,"%s : Failed to poll DLSTAT UC01 register for (%s):(%d)\n", __FUNCTION__, device->name, link->linkNumber); } params->dlstatLinkIntr = NVSWITCH_MINION_LINK_RD32_LS10(device, link->linkNumber, _MINION, _NVLINK_LINK_INTR(localLinkNumber)); return NVL_SUCCESS; }