/* * t19x-nvlink-endpt-ioctl.c: * This file adds various IOCTLs 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 #include #include #include "t19x-nvlink-endpt.h" #include "nvlink-hw.h" #define TNVLINK_LINK_ID_TO_MASK(link_id) BIT(link_id) static int get_nvlink_caps_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_nvlink_status_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int clear_counters_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_counters_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_err_info_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_error_recoveries_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int setup_eom_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int train_intranode_conn_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_lp_counters_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int clear_lp_counters_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int enable_shim_driver_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int enable_device_interrupts_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int service_device_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int disable_device_interrupts_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int inject_err_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int set_link_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_link_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int set_tx_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_tx_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int set_rx_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_rx_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int write_discovery_token_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int read_discovery_token_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int get_local_pci_info_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int set_topology_info_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int interface_disable_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); static int finalize_shutdown_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct); struct tnvlink_ioctl { const char *const name; const size_t struct_size; int (*handler)(struct tnvlink_dev *, void *); bool is_rm_shim_ioctl; }; static const struct tnvlink_ioctl ioctls[] = { [TNVLINK_IOCTL_GET_NVLINK_CAPS] = { .name = "get_nvlink_caps", .struct_size = sizeof(struct tegra_nvlink_caps), .handler = get_nvlink_caps_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_GET_NVLINK_STATUS] = { .name = "get_nvlink_status", .struct_size = sizeof(struct tegra_nvlink_status), .handler = get_nvlink_status_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_CLEAR_COUNTERS] = { .name = "clear_counters", .struct_size = sizeof(struct tegra_nvlink_clear_counters), .handler = clear_counters_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_GET_COUNTERS] = { .name = "get_counters", .struct_size = sizeof(struct tegra_nvlink_get_counters), .handler = get_counters_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_GET_ERR_INFO] = { .name = "get_err_info", .struct_size = sizeof(struct tegra_nvlink_get_err_info), .handler = get_err_info_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_GET_ERROR_RECOVERIES] = { .name = "get_error_recoveries", .struct_size = sizeof(struct tegra_nvlink_get_error_recoveries), .handler = get_error_recoveries_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_SETUP_EOM] = { .name = "setup_eom", .struct_size = sizeof(struct tegra_nvlink_setup_eom), .handler = setup_eom_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_TRAIN_INTRANODE_CONN] = { .name = "train_intranode_conn", .struct_size = sizeof(struct tegra_nvlink_train_intranode_conn), .handler = train_intranode_conn_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_GET_LP_COUNTERS] = { .name = "get_lp_counters", .struct_size = sizeof(struct tegra_nvlink_get_lp_counters), .handler = get_lp_counters_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_CLEAR_LP_COUNTERS] = { .name = "clear_lp_counters", .struct_size = sizeof(struct tegra_nvlink_clear_lp_counters), .handler = clear_lp_counters_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_ENABLE_SHIM_DRIVER] = { .name = "enable_shim_driver", .struct_size = 0, .handler = enable_shim_driver_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_ENABLE_DEVICE_INTERRUPTS] = { .name = "enable_device_interrupts", .struct_size = sizeof(struct tegra_nvlink_enable_device_interrupts), .handler = enable_device_interrupts_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_SERVICE_DEVICE] = { .name = "service_device", .struct_size = sizeof(struct tegra_nvlink_service_device), .handler = service_device_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_DISABLE_DEVICE_INTERRUPTS] = { .name = "disable_device_interrupts", .struct_size = sizeof(struct tegra_nvlink_disable_device_interrupts), .handler = disable_device_interrupts_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_INJECT_ERR] = { .name = "inject_err", .struct_size = sizeof(struct tegra_nvlink_inject_err), .handler = inject_err_ioctl, .is_rm_shim_ioctl = false, }, [TNVLINK_IOCTL_SET_LINK_MODE] = { .name = "set_link_mode", .struct_size = sizeof(struct tegra_nvlink_set_link_mode), .handler = set_link_mode_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_GET_LINK_MODE] = { .name = "get_link_mode", .struct_size = sizeof(struct tegra_nvlink_get_link_mode), .handler = get_link_mode_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_SET_TX_MODE] = { .name = "set_tx_mode", .struct_size = sizeof(struct tegra_nvlink_set_tx_mode), .handler = set_tx_mode_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_GET_TX_MODE] = { .name = "get_tx_mode", .struct_size = sizeof(struct tegra_nvlink_get_tx_mode), .handler = get_tx_mode_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_SET_RX_MODE] = { .name = "set_rx_mode", .struct_size = sizeof(struct tegra_nvlink_set_rx_mode), .handler = set_rx_mode_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_GET_RX_MODE] = { .name = "get_rx_mode", .struct_size = sizeof(struct tegra_nvlink_get_rx_mode), .handler = get_rx_mode_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_WRITE_DISCOVERY_TOKEN] = { .name = "write_discovery_token", .struct_size = sizeof(struct tegra_nvlink_write_discovery_token), .handler = write_discovery_token_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_READ_DISCOVERY_TOKEN] = { .name = "read_discovery_token", .struct_size = sizeof(struct tegra_nvlink_read_discovery_token), .handler = read_discovery_token_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_GET_LOCAL_PCI_INFO] = { .name = "get_local_pci_info", .struct_size = sizeof(struct tegra_nvlink_get_local_pci_info), .handler = get_local_pci_info_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_SET_TOPOLOGY_INFO] = { .name = "set_topology_info", .struct_size = sizeof(struct tegra_nvlink_set_topology_info), .handler = set_topology_info_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_INTERFACE_DISABLE] = { .name = "interface_disable", .struct_size = 0, .handler = interface_disable_ioctl, .is_rm_shim_ioctl = true, }, [TNVLINK_IOCTL_FINALIZE_SHUTDOWN] = { .name = "finalize_shutdown", .struct_size = 0, .handler = finalize_shutdown_ioctl, .is_rm_shim_ioctl = true, }, }; static bool is_nvlink_loopback_topology(struct tnvlink_dev *tdev) { struct nvlink_device *ndev = tdev->ndev; struct nvlink_link *link = &ndev->link; if (link->device_id == NVLINK_ENDPT_T19X && link->remote_dev_info.device_id == NVLINK_ENDPT_T19X) return true; return false; } static int get_nvlink_caps_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_caps *caps = (struct tegra_nvlink_caps *)ioctl_struct; caps->nvlink_caps |= TEGRA_CTRL_NVLINK_CAPS_SUPPORTED | TEGRA_CTRL_NVLINK_CAPS_VALID; /* Sysmem atomics are supported for NVLINK versions > 1.0 */ if (NVLINK_IP_VERSION > TEGRA_NVLINK_VERSION_10) caps->nvlink_caps |= TEGRA_CTRL_NVLINK_CAPS_SYSMEM_ATOMICS; switch (NVLINK_IP_VERSION) { case TEGRA_NVLINK_VERSION_22: caps->lowest_nvlink_version = TEGRA_CTRL_NVLINK_CAPS_NVLINK_VERSION_2_2; caps->highest_nvlink_version = TEGRA_CTRL_NVLINK_CAPS_NVLINK_VERSION_2_2; caps->lowest_nci_version = TEGRA_CTRL_NVLINK_CAPS_NCI_VERSION_2_2; caps->highest_nci_version = TEGRA_CTRL_NVLINK_CAPS_NCI_VERSION_2_2; /* Supported power states */ caps->nvlink_caps |= TEGRA_CTRL_NVLINK_CAPS_POWER_STATE_L0; caps->nvlink_caps |= TEGRA_CTRL_NVLINK_CAPS_POWER_STATE_L2; break; case TEGRA_NVLINK_VERSION_20: caps->lowest_nvlink_version = TEGRA_CTRL_NVLINK_CAPS_NVLINK_VERSION_2_0; caps->highest_nvlink_version = TEGRA_CTRL_NVLINK_CAPS_NVLINK_VERSION_2_0; caps->lowest_nci_version = TEGRA_CTRL_NVLINK_CAPS_NCI_VERSION_2_0; caps->highest_nci_version = TEGRA_CTRL_NVLINK_CAPS_NCI_VERSION_2_0; /* Supported power states */ caps->nvlink_caps |= TEGRA_CTRL_NVLINK_CAPS_POWER_STATE_L0; break; default: caps->lowest_nvlink_version = TEGRA_CTRL_NVLINK_CAPS_NVLINK_VERSION_1_0; caps->highest_nvlink_version = TEGRA_CTRL_NVLINK_CAPS_NVLINK_VERSION_1_0; caps->lowest_nci_version = TEGRA_CTRL_NVLINK_CAPS_NCI_VERSION_1_0; caps->highest_nci_version = TEGRA_CTRL_NVLINK_CAPS_NCI_VERSION_1_0; /* Supported power states */ caps->nvlink_caps |= TEGRA_CTRL_NVLINK_CAPS_POWER_STATE_L0; break; } /* * Discovered = discovered in discovery table * Enabled = present in discovery table and not disabled through a * registry key * * Since we don't use the discovery table right now and we don't have * registry keys for disabling links, just return 0x1 for both the * disovered and enabled link masks. * TODO: Set the discovered and enabled link masks based on the * discovery table + registry keys (if we implement these) */ caps->discovered_link_mask = TNVLINK_LINK_ID_TO_MASK(0); caps->enabled_link_mask = TNVLINK_LINK_ID_TO_MASK(0); return 0; } static int get_nvlink_status_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct nvlink_device *ndev = tdev->ndev; struct nvlink_link *nlink = &ndev->link; struct tegra_nvlink_status *status = (struct tegra_nvlink_status *)ioctl_struct; struct nvlink_device_pci_info *local_pci_info = &ndev->pci_info; struct nvlink_device_pci_info *remote_pci_info = &nlink->remote_dev_info.pci_info; u32 reg_val = 0; u32 state = 0; if (tdev->rm_shim_enabled) { nlink->is_connected = is_link_connected((struct tnvlink_link *)nlink->priv); } status->link_info.connected = nlink->is_connected; status->link_info.remote_device_link_number = nlink->remote_dev_info.link_id; status->link_info.local_device_link_number = nlink->link_id; status->link_info.local_device_info.device_type = TEGRA_CTRL_NVLINK_DEVICE_INFO_DEVICE_TYPE_TEGRA; status->link_info.local_device_info.domain = local_pci_info->domain; status->link_info.local_device_info.bus = local_pci_info->bus; status->link_info.local_device_info.device = local_pci_info->device; status->link_info.local_device_info.function = local_pci_info->function; status->link_info.local_device_info.pci_device_id = local_pci_info->pci_device_id; status->link_info.remote_device_info.domain = remote_pci_info->domain; status->link_info.remote_device_info.bus = remote_pci_info->bus; status->link_info.remote_device_info.device = remote_pci_info->device; status->link_info.remote_device_info.function = remote_pci_info->function; status->link_info.remote_device_info.pci_device_id = remote_pci_info->pci_device_id; if (nlink->remote_dev_info.device_id == NVLINK_ENDPT_T19X) { status->link_info.loop_property = TEGRA_CTRL_NVLINK_STATUS_LOOP_PROPERTY_LOOPBACK; status->link_info.remote_device_info.device_type = TEGRA_CTRL_NVLINK_DEVICE_INFO_DEVICE_TYPE_TEGRA; } else if (nlink->remote_dev_info.device_id == NVLINK_ENDPT_GV100) { status->link_info.loop_property = TEGRA_CTRL_NVLINK_STATUS_LOOP_PROPERTY_NONE; status->link_info.remote_device_info.device_type = TEGRA_CTRL_NVLINK_DEVICE_INFO_DEVICE_TYPE_GPU; } else { nvlink_err("Invalid remote device ID"); return -ENODEV; } status->link_info.caps |= TEGRA_CTRL_NVLINK_CAPS_VALID; status->enabled_link_mask = TNVLINK_LINK_ID_TO_MASK(nlink->link_id); status->link_info.phy_type = TEGRA_CTRL_NVLINK_STATUS_PHY_NVHS; status->link_info.sublink_width = 8; status->link_info.link_state = t19x_nvlink_get_link_state(ndev); t19x_nvlink_get_tx_sublink_state(ndev, &state); status->link_info.tx_sublink_status = (u8)state; t19x_nvlink_get_rx_sublink_state(ndev, &state); status->link_info.rx_sublink_status = (u8)state; reg_val = nvlw_nvl_readl(tdev, NVL_SL1_CONFIG_RX); if (reg_val & BIT(NVL_SL1_CONFIG_RX_REVERSAL_OVERRIDE)) { /* Overridden */ if (reg_val & BIT(NVL_SL1_CONFIG_RX_LANE_REVERSE)) status->link_info.bLane_reversal = true; else status->link_info.bLane_reversal = false; } else { /* Sensed in HW */ if (reg_val & BIT(NVL_SL1_CONFIG_RX_HW_LANE_REVERSE)) status->link_info.bLane_reversal = true; else status->link_info.bLane_reversal = false; } switch (NVLINK_IP_VERSION) { case TEGRA_NVLINK_VERSION_22: status->link_info.nvlink_version = TEGRA_CTRL_NVLINK_STATUS_NVLINK_VERSION_2_2; status->link_info.nci_version = TEGRA_CTRL_NVLINK_STATUS_NCI_VERSION_2_2; break; case TEGRA_NVLINK_VERSION_20: status->link_info.nvlink_version = TEGRA_CTRL_NVLINK_STATUS_NVLINK_VERSION_2_0; status->link_info.nci_version = TEGRA_CTRL_NVLINK_STATUS_NCI_VERSION_2_0; break; default: status->link_info.nvlink_version = TEGRA_CTRL_NVLINK_STATUS_NVLINK_VERSION_1_0; status->link_info.nci_version = TEGRA_CTRL_NVLINK_STATUS_NCI_VERSION_1_0; break; } status->link_info.phy_version = TEGRA_CTRL_NVLINK_STATUS_NVHS_VERSION_1_0; status->link_info.nvlink_link_clockKHz = ndev->link_bitrate / 1000; if (tdev->refclk == NVLINK_REFCLK_150) status->link_info.nvlink_ref_clk_speedKHz = 150000; else if (tdev->refclk == NVLINK_REFCLK_156) status->link_info.nvlink_ref_clk_speedKHz = 156250; status->link_info.nvlink_common_clock_speedKHz = status->link_info.nvlink_link_clockKHz / 16; status->link_info.nvlink_link_clockMhz = status->link_info.nvlink_link_clockKHz / 1000; status->link_info.nvlink_ref_clk_speedMhz = status->link_info.nvlink_ref_clk_speedKHz / 1000; status->link_info.nvlink_common_clock_speedMhz = status->link_info.nvlink_common_clock_speedKHz / 1000; status->link_info.nvlink_ref_clk_type = TEGRA_CTRL_NVLINK_REFCLK_TYPE_NVHS; return 0; } static int clear_counters_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_clear_counters *clear_counters = (struct tegra_nvlink_clear_counters *)ioctl_struct; u32 reg_val = 0; u32 counter_mask = clear_counters->counter_mask; if (clear_counters->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } if ((counter_mask) & (TEGRA_CTRL_NVLINK_COUNTER_TL_TX0 | TEGRA_CTRL_NVLINK_COUNTER_TL_TX1 | TEGRA_CTRL_NVLINK_COUNTER_TL_RX0 | TEGRA_CTRL_NVLINK_COUNTER_TL_RX1)) { reg_val = nvlw_nvltlc_readl(tdev, NVLTLC_TX_DEBUG_TP_CNTR_CTRL); if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_TL_TX0) reg_val |= BIT(NVLTLC_TX_DEBUG_TP_CNTR_CTRL_RESETTX0); if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_TL_TX1) reg_val |= BIT(NVLTLC_TX_DEBUG_TP_CNTR_CTRL_RESETTX1); nvlw_nvltlc_writel(tdev, NVLTLC_TX_DEBUG_TP_CNTR_CTRL, reg_val); reg_val = nvlw_nvltlc_readl(tdev, NVLTLC_RX_DEBUG_TP_CNTR_CTRL); if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_TL_RX0) reg_val |= BIT(NVLTLC_RX_DEBUG_TP_CNTR_CTRL_RESETRX0); if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_TL_RX1) reg_val |= BIT(NVLTLC_RX_DEBUG_TP_CNTR_CTRL_RESETRX1); nvlw_nvltlc_writel(tdev, NVLTLC_RX_DEBUG_TP_CNTR_CTRL, reg_val); } if ((counter_mask) & (TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L0 | TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L1 | TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L2 | TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L3 | TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L4 | TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L5 | TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L6 | TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L7)) { reg_val = nvlw_nvl_readl(tdev, NVL_SL1_ERROR_COUNT_CTRL); reg_val |= BIT(NVL_SL1_ERROR_COUNT_CTRL_CLEAR_LANE_CRC); reg_val |= BIT(NVL_SL1_ERROR_COUNT_CTRL_CLEAR_RATES); nvlw_nvl_writel(tdev, NVL_SL1_ERROR_COUNT_CTRL, reg_val); } if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_FLIT) { reg_val = nvlw_nvl_readl(tdev, NVL_SL1_ERROR_COUNT_CTRL); reg_val |= BIT(NVL_SL1_ERROR_COUNT_CTRL_CLEAR_FLIT_CRC); reg_val |= BIT(NVL_SL1_ERROR_COUNT_CTRL_CLEAR_RATES); nvlw_nvl_writel(tdev, NVL_SL1_ERROR_COUNT_CTRL, reg_val); } if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_DL_TX_ERR_REPLAY) { reg_val = nvlw_nvl_readl(tdev, NVL_SL0_ERROR_COUNT_CTRL); reg_val |= BIT(NVL_SL0_ERROR_COUNT_CTRL_CLEAR_REPLAY); nvlw_nvl_writel(tdev, NVL_SL0_ERROR_COUNT_CTRL, reg_val); } if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_DL_TX_ERR_RECOVERY) { reg_val = nvlw_nvl_readl(tdev, NVL_ERROR_COUNT_CTRL); reg_val |= BIT(NVL_ERROR_COUNT_CTRL_CLEAR_RECOVERY); nvlw_nvl_writel(tdev, NVL_ERROR_COUNT_CTRL, reg_val); } return 0; } static bool t19x_nvlink_is_lane_reversal(struct tnvlink_dev *tdev) { u32 reg_val; bool lane_reversal; reg_val = nvlw_nvl_readl(tdev, NVL_SL1_CONFIG_RX); if (reg_val & BIT(NVL_SL1_CONFIG_RX_REVERSAL_OVERRIDE)) { if (reg_val & BIT(NVL_SL1_CONFIG_RX_LANE_REVERSE)) lane_reversal = true; else lane_reversal = false; } else { if (reg_val & BIT(NVL_SL1_CONFIG_RX_HW_LANE_REVERSE)) lane_reversal = true; else lane_reversal = false; } return lane_reversal; } static void t19x_nvlink_get_lane_crc_errors(struct tnvlink_dev *tdev, struct tegra_nvlink_get_counters *get_counters) { int i; int lane_id; u32 reg_val; u64 lane_crc_val; u32 counter_mask = get_counters->counter_mask; for (i = 0; i < TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_SIZE; i++) { if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_LANE_L(i)) { lane_id = i; if (t19x_nvlink_is_lane_reversal(tdev)) lane_id = 7 - lane_id; if (lane_id < 4) { reg_val = nvlw_nvl_readl(tdev, NVL_SL1_ERROR_COUNT2_LANECRC); } else { reg_val = nvlw_nvl_readl(tdev, NVL_SL1_ERROR_COUNT3_LANECRC); } switch (lane_id) { case 0: lane_crc_val = (u64) NVL_SL1_ERROR_COUNT2_LANECRC_L0_V( reg_val); get_counters->nvlink_counters[ tegra_nvlink_counter(i)] = lane_crc_val; break; case 1: lane_crc_val = (u64) NVL_SL1_ERROR_COUNT2_LANECRC_L1_V( reg_val); get_counters->nvlink_counters[ tegra_nvlink_counter(i)] = lane_crc_val; break; case 2: lane_crc_val = (u64) NVL_SL1_ERROR_COUNT2_LANECRC_L2_V( reg_val); get_counters->nvlink_counters[ tegra_nvlink_counter(i)] = lane_crc_val; break; case 3: lane_crc_val = (u64) NVL_SL1_ERROR_COUNT2_LANECRC_L3_V( reg_val); get_counters->nvlink_counters[ tegra_nvlink_counter(i)] = lane_crc_val; break; case 4: lane_crc_val = (u64) NVL_SL1_ERROR_COUNT3_LANECRC_L4_V( reg_val); get_counters->nvlink_counters[ tegra_nvlink_counter(i)] = lane_crc_val; break; case 5: lane_crc_val = (u64) NVL_SL1_ERROR_COUNT3_LANECRC_L5_V( reg_val); get_counters->nvlink_counters[ tegra_nvlink_counter(i)] = lane_crc_val; break; case 6: lane_crc_val = (u64) NVL_SL1_ERROR_COUNT3_LANECRC_L6_V( reg_val); get_counters->nvlink_counters[ tegra_nvlink_counter(i)] = lane_crc_val; break; case 7: lane_crc_val = (u64) NVL_SL1_ERROR_COUNT3_LANECRC_L7_V( reg_val); get_counters->nvlink_counters[ tegra_nvlink_counter(i)] = lane_crc_val; break; default: break; } } } } static int get_counters_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_get_counters *get_counters = (struct tegra_nvlink_get_counters *)ioctl_struct; u32 reg_val = 0; u64 reg_low = 0; u64 reg_hi = 0; u32 counter_mask = get_counters->counter_mask; if (get_counters->link_id != tdev->ndev->link.link_id) { nvlink_err("Invalid link ID specified"); return -EINVAL; } if (counter_mask & (TEGRA_CTRL_NVLINK_COUNTER_TL_TX0 | TEGRA_CTRL_NVLINK_COUNTER_TL_TX1 | TEGRA_CTRL_NVLINK_COUNTER_TL_RX0 | TEGRA_CTRL_NVLINK_COUNTER_TL_RX1)) { reg_low = nvlw_nvltlc_readl(tdev, NVLTLC_TX_DEBUG_TP_CNTR0_LO); reg_hi = nvlw_nvltlc_readl(tdev, NVLTLC_TX_DEBUG_TP_CNTR0_HI); if (reg_hi & BIT(NVLTLC_TX_DEBUG_TP_CNTR0_HI_ROLLOVER)) { get_counters->bTx0_tl_counter_overflow = true; reg_hi &= ~BIT(NVLTLC_TX_DEBUG_TP_CNTR0_HI_ROLLOVER); } get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_TX0)] = 0; get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_TX0)] |= ((u64) 0xffffffff & reg_low); get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_TX0)] |= (((u64) 0xffffffff & reg_hi) << 32); reg_low = nvlw_nvltlc_readl(tdev, NVLTLC_TX_DEBUG_TP_CNTR1_LO); reg_hi = nvlw_nvltlc_readl(tdev, NVLTLC_TX_DEBUG_TP_CNTR1_HI); if (reg_hi & BIT(NVLTLC_TX_DEBUG_TP_CNTR1_HI_ROLLOVER)) { get_counters->bTx1_tl_counter_overflow = true; reg_hi &= ~BIT(NVLTLC_TX_DEBUG_TP_CNTR1_HI_ROLLOVER); } get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_TX1)] = 0; get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_TX1)] |= ((u64) 0xffffffff & reg_low); get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_TX1)] |= (((u64) 0xffffffff & reg_hi) << 32); reg_low = nvlw_nvltlc_readl(tdev, NVLTLC_RX_DEBUG_TP_CNTR0_LO); reg_hi = nvlw_nvltlc_readl(tdev, NVLTLC_RX_DEBUG_TP_CNTR0_HI); if (reg_hi & BIT(NVLTLC_RX_DEBUG_TP_CNTR0_HI_ROLLOVER)) { get_counters->bRx0_tl_counter_overflow = true; reg_hi &= ~BIT(NVLTLC_RX_DEBUG_TP_CNTR0_HI_ROLLOVER); } get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_RX0)] = 0; get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_RX0)] |= ((u64) 0xffffffff & reg_low); get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_RX0)] |= (((u64) 0xffffffff & reg_hi) << 32); reg_low = nvlw_nvltlc_readl(tdev, NVLTLC_RX_DEBUG_TP_CNTR1_LO); reg_hi = nvlw_nvltlc_readl(tdev, NVLTLC_RX_DEBUG_TP_CNTR1_HI); if (reg_hi & BIT(NVLTLC_RX_DEBUG_TP_CNTR1_HI_ROLLOVER)) { get_counters->bRx1_tl_counter_overflow = true; reg_hi &= ~BIT(NVLTLC_RX_DEBUG_TP_CNTR1_HI_ROLLOVER); } get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_RX1)] = 0; get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_RX1)] |= ((u64) 0xffffffff & reg_low); get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_TL_RX1)] |= (((u64) 0xffffffff & reg_hi) << 32); } /* Get the count of flit CRC errors */ if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_FLIT) { reg_val = nvlw_nvl_readl(tdev, NVL_SL1_ERROR_COUNT1); get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_DL_RX_ERR_CRC_FLIT)] = (u64)NVL_SL1_ERROR_COUNT1_FLIT_CRC_ERRORS_V(reg_val); } /* Get the count of lane CRC errors */ t19x_nvlink_get_lane_crc_errors(tdev, get_counters); /* Get the count of replays for the link */ if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_DL_TX_ERR_REPLAY) { reg_val = nvlw_nvl_readl(tdev, NVL_SL0_ERROR_COUNT4); get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_DL_TX_ERR_REPLAY)] = (u64) NVL_SL0_ERROR_COUNT4_REPLAY_EVENTS_V(reg_val); } /* Get the count of HW recoveries for the link */ if (counter_mask & TEGRA_CTRL_NVLINK_COUNTER_DL_TX_ERR_RECOVERY) { reg_val = nvlw_nvl_readl(tdev, NVL_ERROR_COUNT1); get_counters->nvlink_counters[TEGRA_BIT_IDX_32( TEGRA_CTRL_NVLINK_COUNTER_DL_TX_ERR_RECOVERY)] = (u64) NVL_ERROR_COUNT1_RECOVERY_EVENTS_V(reg_val); } return 0; } static int get_err_info_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct nvlink_device *ndev = tdev->ndev; struct nvlink_link *nlink = &ndev->link; struct tegra_nvlink_get_err_info *get_err_info = (struct tegra_nvlink_get_err_info *)ioctl_struct; u32 reg_val = 0; u32 state = 0; bool excess_err_dl = false; get_err_info->link_mask = TNVLINK_LINK_ID_TO_MASK(nlink->link_id); get_err_info->link_err_info.tl_err_log = 0; get_err_info->link_err_info.tl_intr_en = 0; get_err_info->link_err_info.tlc_tx_err_status0 = tdev->tlink.tlc_tx_err_status0; get_err_info->link_err_info.tlc_rx_err_status0 = tdev->tlink.tlc_rx_err_status0; get_err_info->link_err_info.tlc_rx_err_status1 = tdev->tlink.tlc_rx_err_status1; get_err_info->link_err_info.tlc_tx_err_log_en0 = nvlw_nvltlc_readl(tdev, NVLTLC_TX_ERR_STATUS_0); get_err_info->link_err_info.tlc_rx_err_log_en0 = nvlw_nvltlc_readl(tdev, NVLTLC_RX_ERR_STATUS_0); get_err_info->link_err_info.tlc_rx_err_log_en1 = nvlw_nvltlc_readl(tdev, NVLTLC_RX_ERR_STATUS_1); /* Reset Errlog after clients get the value */ tdev->tlink.tlc_tx_err_status0 = 0; tdev->tlink.tlc_rx_err_status0 = 0; tdev->tlink.tlc_rx_err_status1 = 0; /* MIF blocks doesn't exist on tegra. Hence 0 the mif err fields */ get_err_info->link_err_info.mif_tx_err_status0 = 0; get_err_info->link_err_info.mif_rx_err_status0 = 0; t19x_nvlink_get_tx_sublink_state(ndev, &state); get_err_info->link_err_info.dl_speed_status_tx = state; t19x_nvlink_get_rx_sublink_state(ndev, &state); get_err_info->link_err_info.dl_speed_status_rx = state; if (nvlw_nvl_readl(tdev, NVL_INTR_STALL_EN) & BIT(NVL_INTR_STALL_EN_RX_SHORT_ERROR_RATE)) { reg_val = nvlw_nvl_readl(tdev, NVL_INTR); if (reg_val & BIT(NVL_INTR_RX_SHORT_ERROR_RATE)) { excess_err_dl = true; nvlw_nvl_writel(tdev, NVL_INTR, reg_val); } } get_err_info->link_err_info.bExcess_error_dl = excess_err_dl; return 0; } /* Get the number of successful error recoveries */ static int get_error_recoveries_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_get_error_recoveries *get_err_recoveries = (struct tegra_nvlink_get_error_recoveries *)ioctl_struct; if (get_err_recoveries->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } get_err_recoveries->num_recoveries = tdev->tlink.error_recoveries; /* Clear the counts */ tdev->tlink.error_recoveries = 0; return 0; } static int setup_eom_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_setup_eom *setup_eom = (struct tegra_nvlink_setup_eom *)ioctl_struct; if (setup_eom->link_id != tdev->ndev->link.link_id) { nvlink_err("Invalid link ID specified"); return -EINVAL; } return minion_send_cmd(tdev, MINION_NVLINK_DL_CMD_COMMAND_CONFIGEOM, setup_eom->params); } static void nvlink_get_endpoint_state(struct tnvlink_dev *tdev, struct tegra_nvlink_link_state *link_state) { struct nvlink_device *ndev = tdev->ndev; link_state->link_mode = t19x_nvlink_get_link_mode(ndev); link_state->tx_sublink_mode = t19x_nvlink_get_sublink_mode(ndev, 0); link_state->rx_sublink_mode = t19x_nvlink_get_sublink_mode(ndev, 1); } static int train_intranode_conn_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct nvlink_device *ndev = tdev->ndev; struct tegra_nvlink_train_intranode_conn *train_intranode_conn = (struct tegra_nvlink_train_intranode_conn *)ioctl_struct; int ret; if (tdev->rm_shim_enabled) { nvlink_err("The TRAIN_INTRANODE_CONN IOCTL is currently" " disabled because the RM shim driver is enabled. When" " the shim driver is enabled," " link state transitions/link training are handled by" " the RM NVLINK core driver. Therefore, the" " TRAIN_INTRANODE_CONN IOCTL is not needed when the RM" " shim driver is enabled. If you want to enable this" " IOCTL, disable the shim driver mode in the Tegra" " NVLINK endpoint driver."); ret = -ENOSYS; goto exit; } if (train_intranode_conn->src_end_point.node_id != train_intranode_conn->dst_end_point.node_id) { nvlink_err("Source and destination node IDs don't match!"); ret = -EINVAL; goto exit; } if (train_intranode_conn->src_end_point.link_index != ndev->link.link_id) { nvlink_err("Source link ID mismatch!"); ret = -EINVAL; goto exit; } if (train_intranode_conn->dst_end_point.link_index != ndev->link.remote_dev_info.link_id) { nvlink_err("Destination link ID mismatch!"); ret = -EINVAL; goto exit; } switch (train_intranode_conn->train_to) { case tegra_nvlink_train_conn_off_to_swcfg: ret = nvlink_transition_intranode_conn_off_to_safe(ndev); break; case tegra_nvlink_train_conn_swcfg_to_active: ret = nvlink_train_intranode_conn_safe_to_hs(ndev); break; case tegra_nvlink_train_conn_to_off: /* OFF state transitions are not supported/tested */ nvlink_err("OFF state transitions are not supported"); ret = -EINVAL; break; case tegra_nvlink_train_conn_active_to_swcfg: ret = nvlink_transition_intranode_conn_hs_to_safe(ndev); break; case tegra_nvlink_train_conn_swcfg_to_off: /* OFF state transitions are not supported/tested */ nvlink_err("OFF state transitions are not supported"); ret = -EINVAL; break; default: nvlink_err("Invalid training mode specified"); ret = -EINVAL; break; } nvlink_get_endpoint_state(tdev, &train_intranode_conn->src_end_state); if (is_nvlink_loopback_topology(tdev)) { train_intranode_conn->dst_end_state.link_mode = train_intranode_conn->src_end_state.link_mode; train_intranode_conn->dst_end_state.tx_sublink_mode = train_intranode_conn->src_end_state.tx_sublink_mode; train_intranode_conn->dst_end_state.rx_sublink_mode = train_intranode_conn->src_end_state.rx_sublink_mode; } else { /* TODO: */ /* Handle other topologies */ } exit: train_intranode_conn->status = ret; return ret; } /* * Get count for LP hardware counters that are specified in counter_valid_mask. * Clear unsupported ones in counter_valid_mask. */ static int get_lp_counters_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_get_lp_counters *get_lp_counters = (struct tegra_nvlink_get_lp_counters *)ioctl_struct; u32 counter_valid_mask_out = 0; u32 counter_valid_mask = get_lp_counters->counter_valid_mask; u32 reg_val; u32 cnt_idx; if (get_lp_counters->link_id != tdev->ndev->link.link_id) { nvlink_err("Invalid link ID specified"); return -EINVAL; } cnt_idx = TEGRA_CTRL_NVLINK_GET_LP_COUNTERS_COUNT_TX_NVHS; if (counter_valid_mask & BIT(cnt_idx)) { reg_val = nvlw_nvl_readl(tdev, NVL_STATS_A); get_lp_counters->counter_values[cnt_idx] = NVL_STATS_A_COUNT_TX_STATE_NVHS_V(reg_val); counter_valid_mask_out |= BIT(cnt_idx); } cnt_idx = TEGRA_CTRL_NVLINK_GET_LP_COUNTERS_COUNT_TX_EIGHTH; if (counter_valid_mask & BIT(cnt_idx)) { reg_val = nvlw_nvl_readl(tdev, NVL_STATS_A); get_lp_counters->counter_values[cnt_idx] = NVL_STATS_A_COUNT_TX_STATE_EIGHTH_V(reg_val); counter_valid_mask_out |= BIT(cnt_idx); } cnt_idx = TEGRA_CTRL_NVLINK_GET_LP_COUNTERS_COUNT_TX_OTHER; if (counter_valid_mask & BIT(cnt_idx)) { reg_val = nvlw_nvl_readl(tdev, NVL_STATS_B); get_lp_counters->counter_values[cnt_idx] = NVL_STATS_B_COUNT_TX_STATE_OTHER_V(reg_val); counter_valid_mask_out |= BIT(cnt_idx); } cnt_idx = TEGRA_CTRL_NVLINK_GET_LP_COUNTERS_NUM_TX_LP_ENTER; if (counter_valid_mask & BIT(cnt_idx)) { reg_val = nvlw_nvl_readl(tdev, NVL_STATS_D); get_lp_counters->counter_values[cnt_idx] = NVL_STATS_D_NUM_TX_LP_ENTER_V(reg_val); counter_valid_mask_out |= BIT(cnt_idx); } cnt_idx = TEGRA_CTRL_NVLINK_GET_LP_COUNTERS_NUM_TX_LP_EXIT; if (counter_valid_mask & BIT(cnt_idx)) { reg_val = nvlw_nvl_readl(tdev, NVL_STATS_D); get_lp_counters->counter_values[cnt_idx] = NVL_STATS_D_NUM_TX_LP_EXIT_V(reg_val); counter_valid_mask_out |= BIT(cnt_idx); } get_lp_counters->counter_valid_mask = counter_valid_mask_out; return 0; } static int clear_lp_counters_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_clear_lp_counters *clear_lp_counters = (struct tegra_nvlink_clear_lp_counters *)ioctl_struct; u32 reg_val = 0; if (clear_lp_counters->link_id != tdev->ndev->link.link_id) { nvlink_err("Invalid link ID specified"); return -EINVAL; } reg_val = nvlw_nvl_readl(tdev, NVL_STATS_CTRL); reg_val |= BIT(NVL_STATS_CTRL_CLEAR_ALL); nvlw_nvl_writel(tdev, NVL_STATS_CTRL, reg_val); return 0; } static int enable_shim_driver_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct nvlink_device *ndev = tdev->ndev; tdev->rm_shim_enabled = true; ndev->device_id = NVLINK_ENDPT_T19X; ndev->link.device_id = ndev->device_id; ndev->is_master = false; /* * Right now we only support a T19x+GV100 topology for the RM shim * mode */ ndev->link.remote_dev_info.device_id = NVLINK_ENDPT_GV100; /* * In RM shim driver mode we use the RM core driver instead of the Tegra * core driver. Therefore, we're unregistering from the Tegra core * driver. */ nvlink_unregister_link(&ndev->link); nvlink_unregister_device(ndev); return 0; } static int enable_device_interrupts_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_enable_device_interrupts *device_interrupts = (struct tegra_nvlink_enable_device_interrupts *)ioctl_struct; if (device_interrupts->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } nvlink_enable_dl_interrupts(tdev); return 0; } static int service_device_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { int ret = 0; struct tegra_nvlink_service_device *service_device = (struct tegra_nvlink_service_device *)ioctl_struct; bool retrain_from_safe = false; if (service_device->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } ret = nvlink_service_dl_interrupts(tdev, &retrain_from_safe); if ((ret == 0) && retrain_from_safe) { service_device->retrain_from_safe_mask = TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id); } return ret; } static int disable_device_interrupts_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_disable_device_interrupts *device_interrupts = (struct tegra_nvlink_disable_device_interrupts *)ioctl_struct; if (device_interrupts->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } nvlink_disable_dl_interrupts(tdev); return 0; } static int inject_err_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { int ret = 0; struct tegra_nvlink_inject_err *inject_err = (struct tegra_nvlink_inject_err *)ioctl_struct; if (inject_err->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } if (inject_err->is_fatal_error) { /* * Choice of error is somewhat arbitrary. If needed, we can add * a field in struct tegra_nvlink_inject_err to allow userspace * to control which specific error they want to inject. */ nvlink_dbg("Injecting RAM data parity error on link"); nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_INJECT_0, BIT(NVLTLC_RX_ERR_INJECT_0_RXRAMDATAPARITYERR)); } else { nvlink_dbg("Injecting RCVY_AC error on link"); ret = t19x_nvlink_set_link_mode(tdev->ndev, NVLINK_LINK_RCVY_AC); } return ret; } static int set_link_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_set_link_mode *set_link_mode = (struct tegra_nvlink_set_link_mode *)ioctl_struct; if (set_link_mode->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } return t19x_nvlink_set_link_mode(tdev->ndev, set_link_mode->link_mode); } static int get_link_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_get_link_mode *get_link_mode = (struct tegra_nvlink_get_link_mode *)ioctl_struct; if (get_link_mode->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } get_link_mode->link_mode = t19x_nvlink_get_link_mode(tdev->ndev); return 0; } static int set_tx_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_set_tx_mode *set_tx_mode = (struct tegra_nvlink_set_tx_mode *)ioctl_struct; if (set_tx_mode->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } return t19x_nvlink_set_sublink_mode(tdev->ndev, false, set_tx_mode->tx_mode); } static int get_tx_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_get_tx_mode *get_tx_mode = (struct tegra_nvlink_get_tx_mode *)ioctl_struct; if (get_tx_mode->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } get_tx_mode->tx_mode = t19x_nvlink_get_sublink_mode(tdev->ndev, false); return 0; } static int set_rx_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_set_rx_mode *set_rx_mode = (struct tegra_nvlink_set_rx_mode *)ioctl_struct; if (set_rx_mode->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } return t19x_nvlink_set_sublink_mode(tdev->ndev, true, set_rx_mode->rx_mode); } static int get_rx_mode_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_get_rx_mode *get_rx_mode = (struct tegra_nvlink_get_rx_mode *)ioctl_struct; if (get_rx_mode->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } get_rx_mode->rx_mode = t19x_nvlink_get_sublink_mode(tdev->ndev, true); return 0; } static int write_discovery_token_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_write_discovery_token *write_discovery_token = (struct tegra_nvlink_write_discovery_token *)ioctl_struct; if (write_discovery_token->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } return t19x_nvlink_write_discovery_token(tdev, write_discovery_token->token); } static int read_discovery_token_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_read_discovery_token *read_discovery_token = (struct tegra_nvlink_read_discovery_token *)ioctl_struct; if (read_discovery_token->link_mask != TNVLINK_LINK_ID_TO_MASK(tdev->ndev->link.link_id)) { nvlink_err("Invalid link mask specified"); return -EINVAL; } return t19x_nvlink_read_discovery_token(tdev, &read_discovery_token->token); } static int get_local_pci_info_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { struct tegra_nvlink_get_local_pci_info *get_local_pci_info = (struct tegra_nvlink_get_local_pci_info *)ioctl_struct; struct tegra_nvlink_device_info *user_local_endpt = &get_local_pci_info->local_endpt; struct nvlink_device_pci_info *kernel_local_endpt = &tdev->ndev->pci_info; user_local_endpt->domain = kernel_local_endpt->domain; user_local_endpt->bus = kernel_local_endpt->bus; user_local_endpt->device = kernel_local_endpt->device; user_local_endpt->function = kernel_local_endpt->function; user_local_endpt->pci_device_id = kernel_local_endpt->pci_device_id; user_local_endpt->device_type = TEGRA_CTRL_NVLINK_DEVICE_INFO_DEVICE_TYPE_TEGRA; return 0; } static int set_topology_info_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { int ret = 0; struct tegra_nvlink_set_topology_info *set_topology_info = (struct tegra_nvlink_set_topology_info *)ioctl_struct; struct tegra_nvlink_device_info *user_remote_dev_info = &set_topology_info->remote_endpt; struct nvlink_device *ndev = tdev->ndev; struct nvlink_link *nlink = &ndev->link; struct nvlink_device_pci_info *kernel_local_pci_info = &ndev->pci_info; struct nvlink_device_pci_info *kernel_remote_pci_info = &nlink->remote_dev_info.pci_info; if (user_remote_dev_info->device_type != TEGRA_CTRL_NVLINK_DEVICE_INFO_DEVICE_TYPE_GPU) { nvlink_err("Invalid remote endpoint type (type = 0x%llx)", user_remote_dev_info->device_type); ret = -EINVAL; goto fail; } nlink->link_id = set_topology_info->local_link_id; nlink->remote_dev_info.link_id = set_topology_info->remote_link_id; kernel_remote_pci_info->domain = user_remote_dev_info->domain; kernel_remote_pci_info->bus = user_remote_dev_info->bus; kernel_remote_pci_info->device = user_remote_dev_info->device; kernel_remote_pci_info->function = user_remote_dev_info->function; kernel_remote_pci_info->pci_device_id = user_remote_dev_info->pci_device_id; if (memcmp(kernel_remote_pci_info, kernel_local_pci_info, sizeof(*kernel_remote_pci_info)) == 0) { nvlink_err("PCI information of remote and local devices is" " identical. This is an invalid configuration."); ret = -EINVAL; goto fail; } goto exit; fail: /* In the case of an error, zero out all stored information. */ nlink->link_id = 0; nlink->remote_dev_info.link_id = 0; memset(kernel_remote_pci_info, 0, sizeof(*kernel_remote_pci_info)); exit: return ret; } static int interface_disable_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { return t19x_nvlink_dev_interface_disable(tdev->ndev); } static int finalize_shutdown_ioctl(struct tnvlink_dev *tdev, void *ioctl_struct) { int ret = 0; ret = nvlink_set_init_state(tdev->ndev, NVLINK_DEV_OFF); if (ret < 0) nvlink_err("Failed to set init_state to NVLINK_DEV_OFF"); return ret; } static long t19x_nvlink_endpt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct tnvlink_dev *tdev = file->private_data; enum tnvlink_ioctl_num ioctl_num = _IOC_NR(cmd); u32 ioc_dir = _IOC_DIR(cmd); u32 arg_size = _IOC_SIZE(cmd); void *arg_copy = NULL; int ret = 0; if (!tdev) { nvlink_err("Invalid Tegra nvlink device"); ret = -ENODEV; goto fail; } if ((_IOC_TYPE(cmd) != TEGRA_NVLINK_IOC_MAGIC) || (ioctl_num < 0) || (ioctl_num >= TNVLINK_IOCTL_NUM_IOCTLS)) { nvlink_err("Unsupported IOCTL call"); return -EINVAL; } if (!tdev->rm_shim_enabled && ioctls[ioctl_num].is_rm_shim_ioctl && ioctl_num != TNVLINK_IOCTL_ENABLE_SHIM_DRIVER) { nvlink_err("The %s IOCTL can only be called when the RM shim" " driver is being used. Currently the RM shim driver is" " disabled. Therefore, the %s IOCTL has been" " disabled as well.", ioctls[ioctl_num].name, ioctls[ioctl_num].name); return -ENOSYS; } if (arg_size != ioctls[ioctl_num].struct_size) { nvlink_err("Invalid IOCTL struct passed from userspace"); ret = -EINVAL; goto fail; } /* Only allocate a buffer if the IOCTL needs a buffer */ if (!(ioc_dir & _IOC_NONE)) { arg_copy = kzalloc(arg_size, GFP_KERNEL); if (!arg_copy) { nvlink_err("Can't allocate memory for kernel IOCTL" " struct"); ret = -ENOMEM; goto fail; } } if (ioc_dir & _IOC_WRITE) { if (copy_from_user(arg_copy, (void __user *)arg, arg_size)) { nvlink_err("Failed to copy data from userspace IOCTL" " struct into kernel IOCTL struct"); ret = -EFAULT; goto fail; } } ret = ioctls[ioctl_num].handler(tdev, arg_copy); if (ret < 0) goto fail; if (ioc_dir & _IOC_READ) { if (copy_to_user((void __user *)arg, arg_copy, arg_size)) { nvlink_err("Failed to copy data from kernel IOCTL" " struct into userspace IOCTL struct"); ret = -EFAULT; goto fail; } } nvlink_dbg("The %s IOCTL completed successfully!", ioctls[ioctl_num].name); goto cleanup; fail: nvlink_err("The %s IOCTL failed!", ioctls[ioctl_num].name); cleanup: kfree(arg_copy); return ret; } static int t19x_nvlink_endpt_open(struct inode *in, struct file *filp) { int ret = 0; unsigned int minor = iminor(in); struct tnvlink_dev *tdev = container_of(in->i_cdev, struct tnvlink_dev, cdev); struct nvlink_device *ndev = tdev->ndev; if (minor > 0) { nvlink_err("Incorrect minor number"); return -EBADFD; } if (ndev->is_master) { ret = nvlink_enumerate(ndev); if (ret < 0) nvlink_err("Failed to enable the link!"); else nvlink_dbg("Link enabled successfully!"); } else { /* This case is needed for the RM shim driver mode */ nvlink_dbg("Because Tegra endpoint is not the NVLINK master," " Tegra NVLINK's device node open will only perform" " Tegra NVLINK controller initialization. Link state" " transitions will be initiated later on by the NVLINK" " master endpoint."); ret = nvlink_initialize_endpoint(ndev); } filp->private_data = tdev; return ret; } static ssize_t t19x_nvlink_endpt_read(struct file *file, char __user *ubuf, size_t count, loff_t *offp) { return 0; } static int t19x_nvlink_endpt_release(struct inode *inode, struct file *filp) { return 0; } /* File ops for device node */ const struct file_operations t19x_nvlink_endpt_ops = { .owner = THIS_MODULE, .open = t19x_nvlink_endpt_open, .read = t19x_nvlink_endpt_read, .release = t19x_nvlink_endpt_release, .unlocked_ioctl = t19x_nvlink_endpt_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = t19x_nvlink_endpt_ioctl, #endif };