Files
linux-nv-oot/drivers/misc/nvscic2c-pcie/epc/module.c
Laxman Dewangan f9f8b37e74 pci: Use conftest to find if pci_disable_pcie_error_reporting() present
Use conftest to find if API pci_disable_pcie_error_reporting()
is present. This API is dropped from Linux 6.5 with change
commit 69b264df8a41282 ("PCI/AER: Drop unused
pci_disable_pcie_error_reporting()")

Bug 4346767

Change-Id: Idcaf36e4215a874c70f7457a46fd875f1349b121
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3028741
Reviewed-by: Shardar Mohammed <smohammed@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
2023-12-07 18:21:01 -08:00

571 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#include <nvidia/conftest.h>
#define pr_fmt(fmt) "nvscic2c-pcie: epc: " fmt
#include <linux/aer.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/pci.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/tegra-pcie-edma.h>
#include <linux/types.h>
#include "comm-channel.h"
#include "common.h"
#include "endpoint.h"
#include "iova-alloc.h"
#include "module.h"
#include "pci-client.h"
#include "vmap.h"
static const struct pci_device_id nvscic2c_pcie_epc_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_C2C_1) },
{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_C2C_2) },
{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_C2C_3) },
{},
};
/* wrapper over tegra-pcie-edma init api. */
static int
edma_module_init(struct driver_ctx_t *drv_ctx)
{
u8 i = 0;
int ret = 0;
struct tegra_pcie_edma_init_info info = {0};
if (WARN_ON(!drv_ctx || !drv_ctx->drv_param.edma_np))
return -EINVAL;
memset(&info, 0x0, sizeof(info));
info.np = drv_ctx->drv_param.edma_np;
info.edma_remote = NULL;
for (i = 0; i < DMA_WR_CHNL_NUM; i++) {
info.tx[i].ch_type = EDMA_CHAN_XFER_ASYNC;
info.tx[i].num_descriptors = NUM_EDMA_DESC;
}
/*No use-case for RD channels.*/
drv_ctx->edma_h = tegra_pcie_edma_initialize(&info);
if (!drv_ctx->edma_h)
ret = -ENODEV;
return ret;
}
/* should stop any ongoing eDMA transfers.*/
static void
edma_module_stop(struct driver_ctx_t *drv_ctx)
{
if (!drv_ctx || !drv_ctx->edma_h)
return;
tegra_pcie_edma_stop(drv_ctx->edma_h);
}
/* should not have any ongoing eDMA transfers.*/
static void
edma_module_deinit(struct driver_ctx_t *drv_ctx)
{
if (!drv_ctx || !drv_ctx->edma_h)
return;
tegra_pcie_edma_deinit(drv_ctx->edma_h);
drv_ctx->edma_h = NULL;
}
static void
free_inbound_area(struct pci_dev *pdev, struct dma_buff_t *self_mem)
{
struct driver_ctx_t *drv_ctx = NULL;
if (!pdev || !self_mem)
return;
drv_ctx = pci_get_drvdata(pdev);
if (!drv_ctx)
return;
if (self_mem->dma_handle && drv_ctx->ivd_h)
iova_alloc_deinit(self_mem->dma_handle, self_mem->size,
&drv_ctx->ivd_h);
self_mem->dma_handle = 0x0;
}
/*
* Allocate area visible to PCIe EP/DRV_MODE_EPF. To have symmetry between the
* two modules, even PCIe RP/DRV_MODE_EPC allocates an empty area for all writes
* from PCIe EP/DRV_MODE_EPF to land into. Also, all CPU access from PCIe EP/
* DRV_MODE_EPF need be for one continguous region.
*/
static int
allocate_inbound_area(struct pci_dev *pdev, size_t win_size,
struct dma_buff_t *self_mem)
{
int ret = 0;
struct driver_ctx_t *drv_ctx = NULL;
drv_ctx = pci_get_drvdata(pdev);
if (!drv_ctx) {
pr_err("Could not fetch driver data.");
return -ENOMEM;
}
self_mem->size = win_size;
ret = iova_alloc_init(&pdev->dev, win_size, &self_mem->dma_handle,
&drv_ctx->ivd_h);
if (ret) {
pr_err("iova_alloc_init() failed for size:(0x%lx)\n",
self_mem->size);
}
return ret;
}
static void
free_outbound_area(struct pci_dev *pdev, struct pci_aper_t *peer_mem)
{
if (!pdev || !peer_mem)
return;
peer_mem->aper = 0x0;
peer_mem->size = 0;
}
/* Assign outbound pcie aperture for CPU/eDMA access towards PCIe EP. */
static int
assign_outbound_area(struct pci_dev *pdev, size_t win_size,
struct pci_aper_t *peer_mem)
{
int ret = 0;
peer_mem->size = win_size;
peer_mem->aper = pci_resource_start(pdev, 0);
return ret;
}
/* Handle link message from @DRV_MODE_EPF. */
static void
link_msg_cb(void *data, void *ctx)
{
struct comm_msg *msg = (struct comm_msg *)data;
struct driver_ctx_t *drv_ctx = (struct driver_ctx_t *)ctx;
if (WARN_ON(!msg || !drv_ctx))
return;
if (msg->u.link.status == NVSCIC2C_PCIE_LINK_UP) {
complete_all(&drv_ctx->epc_ctx->epf_ready_cmpl);
} else if (msg->u.link.status == NVSCIC2C_PCIE_LINK_DOWN) {
complete_all(&drv_ctx->epc_ctx->epf_shutdown_cmpl);
} else {
pr_err("(%s): spurious link message received from EPF\n",
drv_ctx->drv_name);
return;
}
/* inidicate link status to application.*/
pci_client_change_link_status(drv_ctx->pci_client_h,
msg->u.link.status);
}
/*
* PCIe subsystem invokes .shutdown()/.remove() handler when the PCIe EP
* is hot-unplugged (gracefully) or @DRV_MODE_EPC(this) is unloaded while
* the PCIe link was still active or when PCIe EP goes abnormal shutdown/
* reboot.
*/
#define MAX_EPF_SHUTDOWN_TIMEOUT_MSEC (5000)
static void
nvscic2c_pcie_epc_remove(struct pci_dev *pdev)
{
int ret = 0;
long timeout = 0;
struct comm_msg msg = {0};
struct driver_ctx_t *drv_ctx = NULL;
if (!pdev)
return;
drv_ctx = pci_get_drvdata(pdev);
if (!drv_ctx)
return;
/*
* send link down message to EPF. EPF apps can stop processing when
* they see this, else the EPF apps can continue processing as EPC
* waits for its apps to close before sending SHUTDOWN msg.
*/
msg.type = COMM_MSG_TYPE_LINK;
msg.u.link.status = NVSCIC2C_PCIE_LINK_DOWN;
ret = comm_channel_ctrl_msg_send(drv_ctx->comm_channel_h, &msg);
if (ret)
pr_err("(%s): Failed to send LINK(DOWN) message\n",
drv_ctx->drv_name);
/* local apps can stop processing if they see this.*/
pci_client_change_link_status(drv_ctx->pci_client_h,
NVSCIC2C_PCIE_LINK_DOWN);
/*
* stop ongoing and pending edma xfers, this edma module shall not
* accept new xfer submissions after this.
*/
edma_module_stop(drv_ctx);
/* wait for @DRV_MODE_EPC (local) endpoints to close. */
ret = endpoints_waitfor_close(drv_ctx->endpoints_h);
if (ret)
pr_err("(%s): Error waiting for endpoints to close\n",
drv_ctx->drv_name);
/*
* Jump to local deinit if any of below condition is true:
* => if PCIe EP SoC went away abruptly already.
* => if PCIe AER received.
*/
if (!pci_device_is_present(pdev) ||
atomic_read(&drv_ctx->epc_ctx->aer_received))
goto deinit;
/*
* Wait for @DRV_MODE_EPF to ACK it's closure too.
*
* Before this @DRV_MODE_EPC(this) endpoints must be closed already
* as @DRV_MODE_EPF in response to this msg shall free all it's
* endpoint mappings.
*/
reinit_completion(&drv_ctx->epc_ctx->epf_shutdown_cmpl);
msg.type = COMM_MSG_TYPE_SHUTDOWN;
if (comm_channel_ctrl_msg_send(drv_ctx->comm_channel_h, &msg)) {
pr_err("(%s): Failed to send shutdown message\n",
drv_ctx->drv_name);
goto deinit;
}
while (timeout == 0) {
timeout = wait_for_completion_interruptible_timeout
(&drv_ctx->epc_ctx->epf_shutdown_cmpl,
msecs_to_jiffies(MAX_EPF_SHUTDOWN_TIMEOUT_MSEC));
if (timeout == -ERESTARTSYS) {
pr_err("(%s): Wait for nvscic2c-pcie-epf to close - interrupted\n",
drv_ctx->drv_name);
} else if (timeout == 0) {
/*
* continue wait only if PCIe EP SoC is still there. It can
* go away abruptly waiting for it's own endpoints to close.
* Also check PCIe AER not received.
*/
if (!pci_device_is_present(pdev)) {
pr_debug("(%s): nvscic2c-pcie-epf went away\n",
drv_ctx->drv_name);
break;
} else if (atomic_read(&drv_ctx->epc_ctx->aer_received)) {
pr_debug("(%s): PCIe AER received\n",
drv_ctx->drv_name);
break;
}
pr_err("(%s): Still waiting for nvscic2c-pcie-epf to close\n",
drv_ctx->drv_name);
} else if (timeout > 0) {
pr_debug("(%s): nvscic2c-pcie-epf closed\n",
drv_ctx->drv_name);
}
}
deinit:
comm_channel_unregister_msg_cb(drv_ctx->comm_channel_h,
COMM_MSG_TYPE_LINK);
endpoints_release(&drv_ctx->endpoints_h);
edma_module_deinit(drv_ctx);
vmap_deinit(&drv_ctx->vmap_h);
comm_channel_deinit(&drv_ctx->comm_channel_h);
pci_client_deinit(&drv_ctx->pci_client_h);
free_outbound_area(pdev, &drv_ctx->peer_mem);
free_inbound_area(pdev, &drv_ctx->self_mem);
pci_release_region(pdev, 0);
pci_clear_master(pdev);
#if defined(NV_PCI_DISABLE_PCIE_ERROR_REPORTING_PRESENT) /* Linux 6.5 */
pci_disable_pcie_error_reporting(pdev);
#endif
pci_disable_device(pdev);
dt_release(&drv_ctx->drv_param);
pci_set_drvdata(pdev, NULL);
kfree(drv_ctx->epc_ctx);
kfree_const(drv_ctx->drv_name);
kfree(drv_ctx);
}
#define MAX_EPF_SETUP_TIMEOUT_MSEC (5000)
static int
nvscic2c_pcie_epc_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
int ret = 0;
char *name = NULL;
size_t win_size = 0;
struct comm_msg msg = {0};
unsigned long timeout = 0;
struct callback_ops cb_ops = {0};
struct driver_ctx_t *drv_ctx = NULL;
struct epc_context_t *epc_ctx = NULL;
struct pci_client_params params = {0};
/* allocate module context.*/
drv_ctx = kzalloc(sizeof(*drv_ctx), GFP_KERNEL);
if (WARN_ON(!drv_ctx))
return -ENOMEM;
name = kasprintf(GFP_KERNEL, "%s-%x", DRIVER_NAME_EPC, id->device);
if (WARN_ON(!name)) {
kfree(drv_ctx);
return -ENOMEM;
}
epc_ctx = kzalloc(sizeof(*epc_ctx), GFP_KERNEL);
if (WARN_ON(!epc_ctx)) {
kfree(name);
kfree(drv_ctx);
return -ENOMEM;
}
init_completion(&epc_ctx->epf_ready_cmpl);
init_completion(&epc_ctx->epf_shutdown_cmpl);
atomic_set(&epc_ctx->aer_received, 0);
drv_ctx->drv_mode = DRV_MODE_EPC;
drv_ctx->drv_name = name;
drv_ctx->epc_ctx = epc_ctx;
pci_set_drvdata(pdev, drv_ctx);
/* check for the device tree node against this Id, must be only one.*/
ret = dt_parse(id->device, drv_ctx->drv_mode, &drv_ctx->drv_param);
if (ret)
goto err_dt_parse;
ret = pcim_enable_device(pdev);
if (ret)
goto err_enable_device;
#if !defined(NV_DROP_PCIE_ERROR_REPORTING)
pci_enable_pcie_error_reporting(pdev);
#endif
pci_set_master(pdev);
ret = pci_request_region(pdev, 0, MODULE_NAME);
if (ret)
goto err_request_region;
win_size = pci_resource_len(pdev, 0);
ret = allocate_inbound_area(pdev, win_size, &drv_ctx->self_mem);
if (ret)
goto err_alloc_inbound;
ret = assign_outbound_area(pdev, win_size, &drv_ctx->peer_mem);
if (ret)
goto err_assign_outbound;
params.dev = &pdev->dev;
params.self_mem = &drv_ctx->self_mem;
params.peer_mem = &drv_ctx->peer_mem;
ret = pci_client_init(&params, &drv_ctx->pci_client_h);
if (ret) {
pr_err("(%s): pci_client_init() failed\n",
drv_ctx->drv_name);
goto err_pci_client;
}
pci_client_save_driver_ctx(drv_ctx->pci_client_h, drv_ctx);
pci_client_save_peer_cpu(drv_ctx->pci_client_h, NVCPU_ORIN);
ret = comm_channel_init(drv_ctx, &drv_ctx->comm_channel_h);
if (ret) {
pr_err("(%s): Failed to initialize comm-channel\n",
drv_ctx->drv_name);
goto err_comm_init;
}
ret = vmap_init(drv_ctx, &drv_ctx->vmap_h);
if (ret) {
pr_err("(%s): Failed to initialize vmap\n",
drv_ctx->drv_name);
goto err_vmap_init;
}
ret = edma_module_init(drv_ctx);
if (ret) {
pr_err("(%s): Failed to initialize edma module\n",
drv_ctx->drv_name);
goto err_edma_init;
}
ret = endpoints_setup(drv_ctx, &drv_ctx->endpoints_h);
if (ret) {
pr_err("(%s): Failed to initialize endpoints\n",
drv_ctx->drv_name);
goto err_endpoints_init;
}
/* register for link status message from @DRV_MODE_EPF (PCIe EP).*/
cb_ops.callback = link_msg_cb;
cb_ops.ctx = (void *)drv_ctx;
ret = comm_channel_register_msg_cb(drv_ctx->comm_channel_h,
COMM_MSG_TYPE_LINK, &cb_ops);
if (ret) {
pr_err("(%s): Failed to register for link message\n",
drv_ctx->drv_name);
goto err_register_msg;
}
/*
* share iova with @DRV_MODE_EPF for it's outbound translation.
* This must be send only after comm-channel, endpoint memory backing
* is created and mapped to self_mem. @DRV_MODE_EPF on seeing this
* message shall send link-up message over comm-channel and possibly
* applications can also start endpoint negotiation, therefore.
*/
reinit_completion(&drv_ctx->epc_ctx->epf_ready_cmpl);
msg.type = COMM_MSG_TYPE_BOOTSTRAP;
msg.u.bootstrap.iova = drv_ctx->self_mem.dma_handle;
msg.u.bootstrap.peer_cpu = NVCPU_ORIN;
ret = comm_channel_ctrl_msg_send(drv_ctx->comm_channel_h, &msg);
if (ret) {
pr_err("(%s): Failed to send comm bootstrap message\n",
drv_ctx->drv_name);
goto err_msg_send;
}
/* wait for @DRV_MODE_EPF to acknowledge it's endpoints are created.*/
timeout =
wait_for_completion_timeout(&drv_ctx->epc_ctx->epf_ready_cmpl,
msecs_to_jiffies(MAX_EPF_SETUP_TIMEOUT_MSEC));
if (timeout == 0) {
ret = -ENOLINK;
pr_err("(%s): Timed-out waiting for nvscic2c-pcie-epf\n",
drv_ctx->drv_name);
goto err_epf_ready;
}
pci_set_drvdata(pdev, drv_ctx);
return ret;
err_epf_ready:
err_msg_send:
comm_channel_unregister_msg_cb(drv_ctx->comm_channel_h,
COMM_MSG_TYPE_LINK);
err_register_msg:
endpoints_release(&drv_ctx->endpoints_h);
err_endpoints_init:
edma_module_deinit(drv_ctx);
err_edma_init:
vmap_deinit(&drv_ctx->vmap_h);
err_vmap_init:
comm_channel_deinit(&drv_ctx->comm_channel_h);
err_comm_init:
pci_client_deinit(&drv_ctx->pci_client_h);
err_pci_client:
free_outbound_area(pdev, &drv_ctx->peer_mem);
err_assign_outbound:
free_inbound_area(pdev, &drv_ctx->self_mem);
err_alloc_inbound:
pci_release_region(pdev, 0);
err_request_region:
pci_clear_master(pdev);
pci_disable_device(pdev);
err_enable_device:
dt_release(&drv_ctx->drv_param);
err_dt_parse:
pci_set_drvdata(pdev, NULL);
kfree_const(drv_ctx->drv_name);
kfree(drv_ctx);
return ret;
}
/*
* Hot-replug is required to recover for both type of errors.
* Hence we will return PCI_ERS_RESULT_DISCONNECT in both cases.
*/
static pci_ers_result_t
nvscic2c_pcie_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
struct driver_ctx_t *drv_ctx = NULL;
if (WARN_ON(!pdev))
return PCI_ERS_RESULT_DISCONNECT;
drv_ctx = pci_get_drvdata(pdev);
if (WARN_ON(!drv_ctx))
return PCI_ERS_RESULT_DISCONNECT;
atomic_set(&drv_ctx->epc_ctx->aer_received, 1);
if (state == pci_channel_io_normal) {
pr_err("AER(NONFATAL) detected for dev %04x:%02x:%02x.%x\n",
pci_domain_nr(pdev->bus),
pdev->bus->number,
PCI_SLOT(pdev->devfn),
PCI_FUNC(pdev->devfn));
(void)pci_client_set_link_aer_error(drv_ctx->pci_client_h,
NVSCIC2C_PCIE_AER_UNCORRECTABLE_NONFATAL);
} else {
if (state == pci_channel_io_frozen) {
pr_err("AER: FATAL detected for dev %04x:%02x:%02x.%x\n",
pci_domain_nr(pdev->bus),
pdev->bus->number,
PCI_SLOT(pdev->devfn),
PCI_FUNC(pdev->devfn));
} else {
pr_err("Unknow error for dev %04x:%02x:%02x.%x treat as AER: FATAL\n",
pci_domain_nr(pdev->bus),
pdev->bus->number,
PCI_SLOT(pdev->devfn),
PCI_FUNC(pdev->devfn));
}
(void)pci_client_set_link_aer_error(drv_ctx->pci_client_h,
NVSCIC2C_PCIE_AER_UNCORRECTABLE_FATAL);
}
/* Mark PCIe Link down and notify all subscribers. */
pci_client_change_link_status(drv_ctx->pci_client_h,
NVSCIC2C_PCIE_LINK_DOWN);
return PCI_ERS_RESULT_DISCONNECT;
}
static struct pci_error_handlers nvscic2c_pcie_error_handlers = {
.error_detected = nvscic2c_pcie_error_detected,
};
MODULE_DEVICE_TABLE(pci, nvscic2c_pcie_epc_tbl);
static struct pci_driver nvscic2c_pcie_epc_driver = {
.name = DRIVER_NAME_EPC,
.id_table = nvscic2c_pcie_epc_tbl,
.probe = nvscic2c_pcie_epc_probe,
.remove = nvscic2c_pcie_epc_remove,
.shutdown = nvscic2c_pcie_epc_remove,
.err_handler = &nvscic2c_pcie_error_handlers,
};
module_pci_driver(nvscic2c_pcie_epc_driver);
#define DRIVER_LICENSE "GPL v2"
#define DRIVER_DESCRIPTION "NVIDIA Chip-to-Chip transfer module for PCIeRP"
#define DRIVER_AUTHOR "Nvidia Corporation"
MODULE_DESCRIPTION(DRIVER_DESCRIPTION);
MODULE_LICENSE(DRIVER_LICENSE);
MODULE_AUTHOR(DRIVER_AUTHOR);