From c71dcfe57948fd591311a2ee769a7d9f033579d8 Mon Sep 17 00:00:00 2001 From: Prasun Kumar Date: Mon, 21 Nov 2022 09:12:31 +0000 Subject: [PATCH] NVIDIA: SAUCE: Add tegra HSI Err Inj driver BugLink: https://bugs.launchpad.net/bugs/2072591 Add tegra HSI Err Inj as platform driver. The tegra-hsierrrptinj driver provides a mechanism for HSI error report transmission from T23x to the MCU communications interface http://nvbugs/3877863 Signed-off-by: Prasun Kumar Tested-by: Abhilash G Reviewed-by: Abhilash G Signed-off-by: Laxman Dewangan Acked-by: Jacob Martin Acked-by: Noah Wager Signed-off-by: Noah Wager --- MAINTAINERS | 8 + drivers/platform/tegra/Kconfig | 9 + drivers/platform/tegra/Makefile | 1 + drivers/platform/tegra/tegra-hsierrrptinj.c | 456 ++++++++++++++++++++ include/linux/tegra-hsierrrptinj.h | 124 ++++++ 5 files changed, 598 insertions(+) create mode 100644 drivers/platform/tegra/tegra-hsierrrptinj.c create mode 100644 include/linux/tegra-hsierrrptinj.h diff --git a/MAINTAINERS b/MAINTAINERS index ffbebe5c5318..c1c697cd6eb5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24447,3 +24447,11 @@ S: Buried alive in reporters T: git git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git F: * F: */ + +HSIERRRPTINJ +M: Prasun Kumar +S: Supported +B: Mobile_Linux_Kernel +F: Documentation/devicetree/bindings/platform/tegra/tegra-hsierrrptinj.yaml +F: drivers/platform/tegra/tegra-hsierrrptinj.c +F: include/linux/tegra-hsierrrptinj.h diff --git a/drivers/platform/tegra/Kconfig b/drivers/platform/tegra/Kconfig index 404281dc1e6e..1c1f59123615 100644 --- a/drivers/platform/tegra/Kconfig +++ b/drivers/platform/tegra/Kconfig @@ -27,4 +27,13 @@ config TEGRA_EPL software detected via HSP mailbox. Access to MISC EC interface is limited to kernel space and is controlled via DT configuration. +config TEGRA_HSIERRRPTINJ + bool "Enable Tegra HSI Error Report Injection client driver" + depends on MAILBOX && TEGRA_EPL && DEBUG_FS + default n + help + The tegra-hsierrrptinj driver provides a + mechanism for HSI error report transmission + from T23x to the MCU communications interface. + endif # TEGRA_PLATFORM_DEVICES diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile index d20e79503472..55e92006a555 100644 --- a/drivers/platform/tegra/Makefile +++ b/drivers/platform/tegra/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_TEGRA_EPL) += tegra-epl.o +obj-$(CONFIG_TEGRA_HSIERRRPTINJ) += tegra-hsierrrptinj.o diff --git a/drivers/platform/tegra/tegra-hsierrrptinj.c b/drivers/platform/tegra/tegra-hsierrrptinj.c new file mode 100644 index 000000000000..e78f442cc994 --- /dev/null +++ b/drivers/platform/tegra/tegra-hsierrrptinj.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.*/ + +/** + * @file tegra-hsierrrptinj.c + * @brief HSI Error Report Injection driver + * + * This file will register as client driver to support triggering + * HSI error reporting from CCPLEX to FSI. + */ + +/* ==================[Includes]============================================= */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linux/tegra-hsierrrptinj.h" + + +/* Format of input buffer */ +/* IP ID : Instance ID : Error Code : Reporter ID : Error Attribute */ +/* "0x0000:0x0000:0x00000000:0x0000:0x00000000" */ +#define ERR_RPT_LEN 43U + +#define NUM_INPUTS 5U + +#define WRITE_USER 0200 /* S_IWUSR */ + +/* Max instance of any registred IP */ +#define MAX_INSTANCE 11U + +/* Timeout in millisec */ +#define TIMEOUT 1000 + +/* Error code for reporting errors to FSI */ +#define HSM_ERROR 0x55 +#define FSI_SW_ERROR 0xAA + +/* EC Index for reporting errors to FSI */ +#define EC_INDEX 0xFFFFFFFF + +/* Error report frame for reporting errors to FSI */ +struct hsm_error_report_frame { + unsigned int type; + unsigned int error_code; + unsigned int reporter_id; + unsigned int ec_index; +}; + +/** + * Note: Any update to IP instances array should be reflected in the macro MAX_INSTANCE. + */ + +/** + * @brief IP Instances + * OTHER - 1 + * GPU - 1 + * EQOS - 1 + * MGBE - 4 + * PCIE - 11 + * PSC - 1 + * I2C = 10 + * QSPI - 2 + * SDMMC - 2 + * TSEC - 1 + * THERM - 1 + * SMMU - 1 + */ +static unsigned int ip_instances[NUM_IPS] = {1, 1, 1, 4, 11, 1, 10, 2, 2, 1, 1, 1}; + +/* This directory entry will point to `/sys/kernel/debug/tegra_hsierrrptinj`. */ +static struct dentry *hsierrrptinj_debugfs_root; + +/* This file will point to `/sys/kernel/debug/tegra_hsierrrptinj/hsierrrpt`. */ +static const char *hsierrrptinj_debugfs_name = "hsierrrpt"; + +/* Data type to store IP Drivers callbacks and auxiliary data */ +struct hsierrrptinj_inj_cb_data { + hsierrrpt_inj cb; + void *data; +}; + +/* This array stores callbacks and auxiliary data registered by IP Drivers */ +static struct hsierrrptinj_inj_cb_data ip_driver_cb_data[NUM_IPS][MAX_INSTANCE] = {NULL}; + +/* Data type for mailbox client and channel details */ +struct hsierrrptinj_hsp_sm { + struct mbox_client client; + struct mbox_chan *chan; +}; + +static struct hsierrrptinj_hsp_sm hsierrrptinj_tx; + + +/* Register Error callbacks from IP Drivers */ +int hsierrrpt_reg_cb(hsierrrpt_ipid_t ip_id, unsigned int instance_id, + hsierrrpt_inj cb_func, void *aux_data) +{ + pr_debug("tegra-hsierrrptinj: Register callback for IP Driver 0x%04x\n", ip_id); + + if (cb_func == NULL) { + pr_err("tegra-hsierrrptinj: Callback function for 0x%04X invalid\n", ip_id); + return -EINVAL; + } + + if (ip_id < 0 || ip_id >= NUM_IPS) { + pr_err("tegra-hsierrrptinj: Invalid IP ID 0x%04x\n", ip_id); + return -EINVAL; + } + + if (instance_id >= ip_instances[ip_id]) { + pr_err("tegra-hsierrrptinj: Invalid instance 0x%04x\n", instance_id); + return -EINVAL; + } + + if (ip_driver_cb_data[ip_id][instance_id].cb != NULL) { + pr_err("tegra-hsierrrptinj: Callback for 0x%04X already registered\n", ip_id); + return -EINVAL; + } + + ip_driver_cb_data[ip_id][instance_id].cb = cb_func; + ip_driver_cb_data[ip_id][instance_id].data = aux_data; + + pr_debug("tegra-hsierrrptinj: Successfully registered callback for 0x%04X\n", ip_id); + + return 0; +} +EXPORT_SYMBOL(hsierrrpt_reg_cb); + +/* De-register Error callbacks from IP Drivers */ +int hsierrrpt_dereg_cb(hsierrrpt_ipid_t ip_id, unsigned int instance_id) +{ + pr_debug("tegra-hsierrrptinj: De-register callback for IP Driver 0x%04x\n", ip_id); + + if (ip_id >= NUM_IPS || ip_id < 0) { + pr_err("tegra-hsierrrptinj: Invalid IP ID 0x%04x\n", ip_id); + return -EINVAL; + } + + if (instance_id >= ip_instances[ip_id]) { + pr_err("tegra-hsierrrptinj: Invalid instance 0x%04x\n", instance_id); + return -EINVAL; + } + + if (ip_driver_cb_data[ip_id][instance_id].cb == NULL) { + pr_err("tegra-hsierrrptinj: Callback for 0x%04X has not been registered\n", ip_id); + return -EINVAL; + } + + ip_driver_cb_data[ip_id][instance_id].cb = NULL; + + pr_debug("tegra-hsierrrptinj: Successfully de-registered callback for 0x%04X\n", ip_id); + + return 0; +} +EXPORT_SYMBOL(hsierrrpt_dereg_cb); + +/* Report errors to FSI */ +static int hsierrrpt_report_to_fsi(struct epl_error_report_frame err_rpt_frame, int ip_id) +{ + int ret = -EINVAL; + + /* Bypass path for reporting errors mapped to Category-1 (local EC) IPs */ + struct hsm_error_report_frame error_report = {0}; + + if (IS_ERR(hsierrrptinj_tx.chan)) { + pr_err("tegra-hsierrrptinj: Mailbox channel or client not initiated\n"); + return -ENODEV; + } + + if (ip_id == IP_HSM) + error_report.type = HSM_ERROR; + else if (ip_id == IP_FSI) + error_report.type = FSI_SW_ERROR; + else + return -EINVAL; + error_report.error_code = err_rpt_frame.error_code; + error_report.reporter_id = err_rpt_frame.reporter_id; + error_report.ec_index = EC_INDEX; + + ret = mbox_send_message(hsierrrptinj_tx.chan, (void *)&error_report); + + return ret < 0 ? ret : 0; +} + +/* Parse user entered data via debugfs interface and trigger IP Driver callback */ +static ssize_t hsierrrptinj_inject(struct file *file, const char __user *buf, + size_t lbuf, loff_t *ppos) +{ + struct epl_error_report_frame error_report = {0}; + int count = 0, ret = -EINVAL; + unsigned long val = 0; + unsigned int ip_id = 0; + unsigned int instance_id = 0x0000; + char ubuf[ERR_RPT_LEN] = {0}; + char *token, *cur = ubuf; + const char *delim = ":"; + + pr_debug("tegra-hsierrrptinj: Inject Error Report\n"); + if (buf == NULL) { + pr_err("tegra-hsierrrptinj: Invalid null input.\n"); + return -EINVAL; + } + + if (lbuf != ERR_RPT_LEN) { + pr_err("tegra-hsierrrptinj: Invalid input length.\n"); + return -EINVAL; + } + + if (copy_from_user(&ubuf, buf, ERR_RPT_LEN)) { + pr_err("tegra-hsierrrptinj: Failed to copy from input buffer.\n"); + return -EFAULT; + } + + pr_debug("tegra-hsierrrptinj: Input Buffer: %s\n", cur); + + /* Extract data and trigger */ + while (count < NUM_INPUTS) { + + token = strsep(&cur, delim); + if (token == NULL) { + pr_err("tegra-hsierrrptinj: Failed to obtain token\n"); + return -EFAULT; + } + + ret = kstrtoul(token, 16, &val); + if (ret < 0) { + pr_err("tegra-hsierrrptinj: Parsing failed. Error: %d\n", ret); + return ret; + } + + switch (count) { + + case 0: /* IP ID */ + pr_debug("tegra-hsierrrptinj: IP ID: 0x%04lx\n", val); + ip_id = val; + count++; + break; + case 1: /* Instance ID */ + pr_debug("tegra-hsierrrptinj: Instance ID: 0x%04lx\n", val); + instance_id = val; + count++; + break; + case 2: /* Error Code */ + pr_debug("tegra-hsierrrptinj: HSI Error ID: 0x%08lx\n", val); + error_report.error_code = val; + count++; + break; + case 3: /* Reporter ID */ + pr_debug("tegra-hsierrrptinj: Reporter ID: 0x%04lx\n", val); + error_report.reporter_id = val; + count++; + break; + case 4: /* Error Attribute */ + pr_debug("tegra-hsierrrptinj: Error Attribute: 0x%08lx\n", val); + error_report.error_attribute = val; + count++; + break; + } + + } + + if (count != NUM_INPUTS) { + pr_err("tegra-hsierrrptinj: Invalid Input format.\n"); + return -EINVAL; + } + + /* Get current timestamp */ + asm volatile("mrs %0, cntvct_el0" : "=r" (error_report.timestamp)); + + /* IPs not in the hsierrrpt_ipid_t list normally report HSI errors to the FSI + * via their local EC, therefore their controlling drivers do not provide a callback. + * Directly send error reports for such IPs to the FSI. + */ + if (ip_id >= NUM_IPS) { + ret = hsierrrpt_report_to_fsi(error_report, ip_id); + pr_debug("tegra-hsierrrptinj: Reporting error to FSI\n"); + goto done; + } + + if (instance_id >= ip_instances[ip_id]) { + pr_err("tegra-hsierrrptinj: Invalid instance for IP Driver 0x%04x\n", ip_id); + return -EINVAL; + } + + /* Trigger IP driver registered callback. If no callback has been registered, + * call the EPD-provided API. + * + * We want certain logging statements to appear in the kernel log with the + * OOTB level configuration. Therefore use pr_err for those statements. + */ + if (ip_driver_cb_data[ip_id][instance_id].cb != NULL) { + ret = ip_driver_cb_data[ip_id][instance_id].cb(instance_id, error_report, + ip_driver_cb_data[ip_id][instance_id].data); + pr_err("tegra-hsierrrptinj: Triggered registered error report callback\n"); + } else { + ret = epl_report_error(error_report); + pr_err("tegra-hsierrrptinj: No registered error report trigger callback found\n"); + pr_err("tegra-hsierrrptinj: Reporting HSI error to FSI directly\n"); + } + +done: + if (ret != 0) { + pr_err("tegra-hsierrrptinj: Failed to report HSI error to FSI\n"); + pr_err("tegra-hsierrrptinj: Error code: %d", ret); + /* Error code has been logged. + * Change error code in case of timeout error + * to prevent re-triggering of .write fops. + */ + if (ret == -ETIME) { + /* Explicitly change error code to -EFAULT */ + ret = -EFAULT; + } + } else { + pr_err("tegra-hsierrrptinj: Successfully reported HSI error to FSI\n"); + /* On success, return the error report length. + * This represents the number of bytes successfully written. + * Returning 0, would re-trigger .write fops + */ + ret = ERR_RPT_LEN; + } + + pr_err("tegra-hsierrrptinj: IP ID: 0x%04x\n", ip_id); + pr_err("tegra-hsierrrptinj: Instance: 0x%04x\n", instance_id); + pr_err("tegra-hsierrrptinj: HSI Error ID: 0x%08x\n", error_report.error_code); + pr_err("tegra-hsierrrptinj: Timestamp: %u\n", error_report.timestamp); + + return ret; +} + +static const struct file_operations hsierrrptinj_fops = { + .read = NULL, + .write = hsierrrptinj_inject, +}; + + +static int __maybe_unused hsierrrptinj_suspend(struct device *dev) +{ + pr_debug("tegra-hsierrrptinj: suspend called\n"); + return 0; +} + +static int __maybe_unused hsierrrptinj_resume(struct device *dev) +{ + pr_debug("tegra-hsierrrptinj: resume called\n"); + return 0; +} +static SIMPLE_DEV_PM_OPS(hsierrrptinj_pm, hsierrrptinj_suspend, hsierrrptinj_resume); + +static const struct of_device_id hsierrrptinj_dt_match[] = { + { .compatible = "nvidia,tegra23x-hsierrrptinj", }, + { } +}; +MODULE_DEVICE_TABLE(of, hsierrrptinj_dt_match); + +static void tegra_hsp_tx_empty_notify(struct mbox_client *cl, void *data, int empty_value) +{ + pr_debug("tegra-hsierrrptinj: TX empty callback came\n"); +} + +static int tegra_hsp_mb_init(struct device *dev) +{ + int err; + + hsierrrptinj_tx.client.dev = dev; + hsierrrptinj_tx.client.tx_block = true; + hsierrrptinj_tx.client.tx_tout = TIMEOUT; + hsierrrptinj_tx.client.tx_done = tegra_hsp_tx_empty_notify; + + hsierrrptinj_tx.chan = mbox_request_channel_byname(&hsierrrptinj_tx.client, + "hsierrrptinj-tx"); + if (IS_ERR(hsierrrptinj_tx.chan)) { + err = PTR_ERR(hsierrrptinj_tx.chan); + pr_err("tegra-hsierrrptinj: Failed to get tx mailbox: %d\n", err); + return err; + } + + return 0; +} + +static int hsierrrptinj_probe(struct platform_device *pdev) +{ + int ret = -EFAULT; + struct dentry *dent = NULL; + struct device *dev = &pdev->dev; + + /* Initiate TX Mailbox */ + ret = tegra_hsp_mb_init(dev); + if (ret != 0) { + pr_err("tegra-hsierrrptinj: Failed initiating tx mailbox\n"); + goto abort; + } + pr_err("tegra-hsierrrptinj: Successfully initiated TX Mailbox\n"); + + /* Create a directory 'tegra_hsierrrptinj' under 'sys/kernel/debug' + * to hold the set of debug files + */ + pr_debug("tegra-hsierrrptinj: Create debugfs directory\n"); + hsierrrptinj_debugfs_root = debugfs_create_dir("tegra_hsierrrptinj", NULL); + if (IS_ERR_OR_NULL(hsierrrptinj_debugfs_root)) { + pr_err("tegra-hsierrrptinj: Failed to create debug directory\n"); + ret = -EFAULT; + goto abort; + } + + /* Create a debug node 'hsierrrpt' under 'sys/kernel/debug/tegra_hsierrrptinj' */ + pr_debug("tegra-hsierrrptinj: Create debugfs node\n"); + dent = debugfs_create_file(hsierrrptinj_debugfs_name, WRITE_USER, + hsierrrptinj_debugfs_root, NULL, &hsierrrptinj_fops); + if (IS_ERR_OR_NULL(dent)) { + pr_err("tegra-hsierrrptinj: Failed to create debugfs node\n"); + ret = -EFAULT; + goto abort; + } + pr_err("tegra-hsierrrptinj: Debug node created successfully\n"); + + pr_debug("tegra-hsierrrptinj: probe success"); + return 0; + +abort: + pr_err("tegra-hsierrrptinj: Failed to create debug node/directory or setup mailbox.\n"); + return ret; +} + +static int hsierrrptinj_remove(struct platform_device *pdev) +{ + /* We must explicitly remove the debugfs entries we created. They are not + * automatically removed upon module removal. + */ + pr_debug("tegra-hsierrrptinj: Recursively remove directory and node created\n"); + debugfs_remove_recursive(hsierrrptinj_debugfs_root); + return 0; +} + +static struct platform_driver hsierrrptinj = { + .driver = { + .name = "hsierrrptinj", + .of_match_table = of_match_ptr(hsierrrptinj_dt_match), + .pm = pm_ptr(&hsierrrptinj_pm), + }, + .probe = hsierrrptinj_probe, + .remove = hsierrrptinj_remove, +}; +module_platform_driver(hsierrrptinj); + +MODULE_DESCRIPTION("tegra: HSI Error Report Injection driver"); +MODULE_AUTHOR("Prasun Kumar "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/tegra-hsierrrptinj.h b/include/linux/tegra-hsierrrptinj.h new file mode 100644 index 000000000000..13b9d0c86139 --- /dev/null +++ b/include/linux/tegra-hsierrrptinj.h @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.*/ + +/** + * @file tegra-hsierrrptinj.h + * @brief HSI Error Report Injection driver header file + * + * This file will expose API prototypes for HSI Error Report Injection kernel + * space APIs. + */ + +#ifndef TEGRA_HSIERRRPTINJ_H +#define TEGRA_HSIERRRPTINJ_H + +/* ==================[Includes]============================================= */ + +#include "tegra-epl.h" + +/* ==================[Type Definitions]===================================== */ +/* Number of registered IPs */ +#define NUM_IPS 12U + +/** + * @brief IP IDs + * + * Note: Any update to this enum must be reflected in the macro NUM_IPS. + */ +typedef enum { +IP_OTHER = 0x0000, +IP_GPU = 0x0001, +IP_EQOS = 0x0002, +IP_MGBE = 0x0003, +IP_PCIE = 0x0004, +IP_PSC = 0x0005, +IP_I2C = 0x0006, +IP_QSPI = 0x0007, +IP_SDMMC = 0x0008, +IP_TSEC = 0x0009, +IP_THERM = 0x000A, +IP_SMMU = 0x000B, + +IP_FSI = 0x00FE, +IP_HSM = 0x00FF +} hsierrrpt_ipid_t; + + +/** + * @brief Callback signature for initiating HSI error reports to FSI + * + * @param[in] instance_id Instance of supported IP. + * @param[in] err_rpt_frame Error frame to be reported. + * @param[in] aux_data Auxiliary data shared by IP drivers + * + * API signature for the common callback function that will be + * implemented by the set of Tegra onchip IP drivers that report HSI + * errors to the FSI. + * + * @returns + * 0 (success) + * -EINVAL (On invalid arguments) + * -EFAULT (On IP driver failure to report error) + * -ETIME (On timeout in IP driver) + */ +typedef int (*hsierrrpt_inj)(unsigned int instance_id, struct epl_error_report_frame err_rpt_frame, + void *aux_data); + +#if IS_ENABLED(CONFIG_TEGRA_HSIERRRPTINJ) + +/** + * @brief HSI error report injection callback registration + * + * @param[in] ip_id Supported IP Id. + * @param[in] instance_id Instance of supported IP. + * @param[in] cb_func Pointer to callback function. + * @param[in] aux_data Auxiliary data shared by IP drivers + * + * API to register the HSI error report trigger callback function + * with the utility. Tegra onchip IP drivers supporting HSI error + * reporting to FSI shall call this API once, at launch time. + * + * @returns + * 0 (success) + * -EINVAL (On invalid arguments) + * -ENODEV (On device driver not loaded) + */ +int hsierrrpt_reg_cb(hsierrrpt_ipid_t ip_id, unsigned int instance_id, hsierrrpt_inj cb_func, + void *aux_data); + +/** + * @brief HSI error report injection callback de-registration + * + * @param[in] ip_id Supported IP Id. + * @param[in] instance_id Instance of supported IP. + * + * + * API to de-register the HSI error report trigger callback function + * with the utility. Tegra onchip IP drivers supporting HSI error + * reporting to FSI shall call this API during exit time. + * + * @returns + * 0 (success) + * -EINVAL (On invalid arguments) + * -ENODEV (On device driver not loaded) + */ +int hsierrrpt_dereg_cb(hsierrrpt_ipid_t ip_id, unsigned int instance_id); + +#else + +static inline int hsierrrpt_reg_cb(hsierrrpt_ipid_t ip_id, unsigned int instance_id, + hsierrrpt_inj cb_func, void *aux_data) +{ + pr_info("tegra-hsierrrptinj: CONFIG_TEGRA_HSIERRRPTINJ not enabled\n"); + return 0; +} + +static inline int hsierrrpt_dereg_cb(hsierrrpt_ipid_t ip_id, unsigned int instance_id) +{ + pr_info("tegra-hsierrrptinj: CONFIG_TEGRA_HSIERRRPTINJ not enabled\n"); + return 0; +} + +#endif /* CONFIG_TEGRA_HSIERRRPTINJ */ + +#endif /* TEGRA_HSIERRRPTINJ_H */