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 <prasunk@nvidia.com>
Tested-by: Abhilash G <abhilashg@nvidia.com>
Reviewed-by: Abhilash G <abhilashg@nvidia.com>
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Acked-by: Jacob Martin <jacob.martin@canonical.com>
Acked-by: Noah Wager <noah.wager@canonical.com>
Signed-off-by: Noah Wager <noah.wager@canonical.com>
This commit is contained in:
Prasun Kumar
2022-11-21 09:12:31 +00:00
committed by Noah Wager
parent bb5eab94be
commit c71dcfe579
5 changed files with 598 additions and 0 deletions
+8
View File
@@ -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 <prasunk@nvidia.com>
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
+9
View File
@@ -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
+1
View File
@@ -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
+456
View File
@@ -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 <b> HSI Error Report Injection driver</b>
*
* This file will register as client driver to support triggering
* HSI error reporting from CCPLEX to FSI.
*/
/* ==================[Includes]============================================= */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/debugfs.h>
#include <linux/pm.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/mailbox_client.h>
#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 <prasunk@nvidia.com>");
MODULE_LICENSE("GPL v2");
+124
View File
@@ -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 <b> HSI Error Report Injection driver header file</b>
*
* 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 */