1511 lines
46 KiB
C
1511 lines
46 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/uaccess.h>
|
|
#include <linux/clk.h>
|
|
#include <uapi/linux/tegra-nvlink-uapi.h>
|
|
|
|
#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
|
|
};
|