NVIDIA: SAUCE: Add tegra EPL driver

BugLink: https://bugs.launchpad.net/bugs/2072591

Add tegra EPL as platform driver. The tegra-epl driver provides
interface for reporting software detected errors to Functional
Safety Island (FSI) via HSP mailbox or MISC EC registers.

http://nvbugs/3847981

Signed-off-by: Rahul Bedarkar <rabedarkar@nvidia.com>
Reviewed-by: Abhilash G <abhilashg@nvidia.com>
Tested-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:
Rahul Bedarkar
2022-10-27 09:09:20 +00:00
committed by Noah Wager
parent cfd5dd4c85
commit e821bb6f75
8 changed files with 553 additions and 0 deletions
+8
View File
@@ -21606,6 +21606,14 @@ M: Jon Hunter <jonathanh@nvidia.com>
S: Supported
F: drivers/dma/tegra*
TEGRA EPL DRIVER
M: Rahul Bedarkar <rabedarkar@nvidia.com>
S: Supported
F: Documentation/devicetree/bindings/platform/tegra/tegra-epl.yaml
F: drivers/platform/tegra/tegra-epl.c
F: include/linux/tegra-epl.h
F: include/uapi/linux/tegra-epl.h
TEGRA I2C DRIVER
M: Laxman Dewangan <ldewangan@nvidia.com>
R: Dmitry Osipenko <digetx@gmail.com>
+2
View File
@@ -14,3 +14,5 @@ source "drivers/platform/olpc/Kconfig"
source "drivers/platform/surface/Kconfig"
source "drivers/platform/x86/Kconfig"
source "drivers/platform/tegra/Kconfig"
+1
View File
@@ -11,3 +11,4 @@ obj-$(CONFIG_OLPC_EC) += olpc/
obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_CHROME_PLATFORMS) += chrome/
obj-$(CONFIG_SURFACE_PLATFORMS) += surface/
obj-$(CONFIG_ARCH_TEGRA) += tegra/
+30
View File
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Tegra Platform Specific Drivers
#
menuconfig TEGRA_PLATFORM_DEVICES
bool "Tegra Platform Specific Device Drivers"
default y
help
Say Y here to get to see options for device drivers of various
Tegra platforms. This option itself does not add any kernel code.
If you say N, all options in this submenu will be skipped and
disabled.
if TEGRA_PLATFORM_DEVICES
config TEGRA_EPL
bool "Tegra Error Propagation Layer Driver"
depends on MAILBOX
help
The tegra-epl driver provides interface for reporting software detected
errors to Functional Safety Island (FSI) via HSP mailbox or MISC EC
registers.
Both user and kernel space interfaces are provided for reporting
software detected via HSP mailbox. Access to MISC EC interface is
limited to kernel space and is controlled via DT configuration.
endif # TEGRA_PLATFORM_DEVICES
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_TEGRA_EPL) += tegra-epl.o
+393
View File
@@ -0,0 +1,393 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/dma-buf.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/mailbox_client.h>
#include <linux/tegra-epl.h>
#include <linux/pm.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
/* Timeout in milliseconds */
#define TIMEOUT 5U
/* 32bit data Length */
#define MAX_LEN 4
/* Macro indicating total number of Misc Sw generic errors in Misc EC */
#define NUM_SW_GENERIC_ERR 5U
/* Error index offset in mission status register */
#define ERROR_INDEX_OFFSET 24U
enum handshake_state {
HANDSHAKE_PENDING,
HANDSHAKE_FAILED,
HANDSHAKE_DONE
};
/* Data type for mailbox client and channel details */
struct epl_hsp_sm {
struct mbox_client client;
struct mbox_chan *chan;
};
/* Data type for accessing TOP2 HSP */
struct epl_hsp {
struct epl_hsp_sm tx;
struct device dev;
};
/* Data type to store Misc Sw Generic error configuration */
struct epl_misc_sw_err_cfg {
void __iomem *err_code_va;
void __iomem *err_assert_va;
const char *dev_configured;
};
static int device_file_major_number;
static const char device_name[] = "epdaemon";
static struct platform_device *pdev_local;
static struct epl_hsp *epl_hsp_v;
static void __iomem *mission_err_status_va;
static bool isAddrMappOk = true;
static struct epl_misc_sw_err_cfg miscerr_cfg[NUM_SW_GENERIC_ERR];
/* State of FSI handshake */
static enum handshake_state hs_state = HANDSHAKE_PENDING;
static DEFINE_MUTEX(hs_state_mutex);
static struct task_struct *fsi_handshake_thread;
static void tegra_hsp_tx_empty_notify(struct mbox_client *cl,
void *data, int empty_value)
{
pr_debug("EPL: TX empty callback came\n");
}
static int tegra_hsp_mb_init(struct device *dev)
{
int err;
epl_hsp_v = devm_kzalloc(dev, sizeof(*epl_hsp_v), GFP_KERNEL);
if (!epl_hsp_v)
return -ENOMEM;
epl_hsp_v->tx.client.dev = dev;
epl_hsp_v->tx.client.tx_block = true;
epl_hsp_v->tx.client.tx_tout = TIMEOUT;
epl_hsp_v->tx.client.tx_done = tegra_hsp_tx_empty_notify;
epl_hsp_v->tx.chan = mbox_request_channel_byname(&epl_hsp_v->tx.client,
"epl-tx");
if (IS_ERR(epl_hsp_v->tx.chan)) {
err = PTR_ERR(epl_hsp_v->tx.chan);
dev_err(dev, "failed to get tx mailbox: %d\n", err);
return err;
}
return 0;
}
static ssize_t device_file_ioctl(
struct file *fp, unsigned int cmd, unsigned long arg)
{
uint32_t lData[MAX_LEN];
int ret;
if (copy_from_user(lData, (void __user *)arg,
MAX_LEN * sizeof(uint32_t)))
return -EACCES;
switch (cmd) {
case EPL_REPORT_ERROR_CMD:
mutex_lock(&hs_state_mutex);
if (hs_state == HANDSHAKE_DONE)
ret = mbox_send_message(epl_hsp_v->tx.chan, (void *) lData);
else
ret = -ENODEV;
mutex_unlock(&hs_state_mutex);
break;
default:
return -EINVAL;
}
return ret;
}
int epl_get_misc_ec_err_status(struct device *dev, uint8_t err_number, bool *status)
{
int ret = -EINVAL;
if (dev && status && (err_number < NUM_SW_GENERIC_ERR)) {
uint32_t mission_err_status = 0U;
uint32_t mask = 0U;
const char *dev_str;
if (miscerr_cfg[err_number].dev_configured == NULL || isAddrMappOk == false)
return -ENODEV;
dev_str = dev_driver_string(dev);
if (strcmp(dev_str, miscerr_cfg[err_number].dev_configured) != 0)
return -EACCES;
mask = (1U << ((ERROR_INDEX_OFFSET + err_number) % 32U));
mission_err_status = readl(mission_err_status_va);
if ((mission_err_status & mask) != 0U)
*status = false;
else
*status = true;
ret = 0;
}
return ret;
}
EXPORT_SYMBOL_GPL(epl_get_misc_ec_err_status);
int epl_report_misc_ec_error(struct device *dev, uint8_t err_number,
uint32_t sw_error_code)
{
int ret = -EINVAL;
bool status = false;
ret = epl_get_misc_ec_err_status(dev, err_number, &status);
if (ret != 0)
return ret;
if (status == false)
return -EAGAIN;
/* Updating error code */
writel(sw_error_code, miscerr_cfg[err_number].err_code_va);
/* triggering SW generic error */
writel(0x1U, miscerr_cfg[err_number].err_assert_va);
return 0;
}
EXPORT_SYMBOL_GPL(epl_report_misc_ec_error);
int epl_report_error(struct epl_error_report_frame error_report)
{
int ret = -EINVAL;
mutex_lock(&hs_state_mutex);
if (epl_hsp_v == NULL || hs_state != HANDSHAKE_DONE) {
mutex_unlock(&hs_state_mutex);
return -ENODEV;
}
mutex_unlock(&hs_state_mutex);
ret = mbox_send_message(epl_hsp_v->tx.chan, (void *)&error_report);
return ret < 0 ? ret : 0;
}
EXPORT_SYMBOL_GPL(epl_report_error);
static int epl_client_fsi_handshake(void *arg)
{
mutex_lock(&hs_state_mutex);
if (epl_hsp_v) {
int ret;
const uint32_t handshake_data[] = {0x45504C48, 0x414E4453, 0x48414B45,
0x44415441};
const uint8_t max_retries = 3;
uint8_t count = 0;
do {
ret = mbox_send_message(epl_hsp_v->tx.chan, (void *) handshake_data);
if (ret < 0) {
hs_state = HANDSHAKE_FAILED;
count++;
} else {
hs_state = HANDSHAKE_DONE;
break;
}
} while (count < max_retries || kthread_should_stop());
}
if (hs_state == HANDSHAKE_FAILED)
pr_warn("epl_client: handshake with FSI failed\n");
else
pr_info("epl_client: handshake done with FSI\n");
mutex_unlock(&hs_state_mutex);
return 0;
}
static int __maybe_unused epl_client_suspend(struct device *dev)
{
pr_debug("tegra-epl: suspend called\n");
mutex_lock(&hs_state_mutex);
hs_state = HANDSHAKE_PENDING;
mutex_unlock(&hs_state_mutex);
return 0;
}
static int __maybe_unused epl_client_resume(struct device *dev)
{
pr_debug("tegra-epl: resume called\n");
fsi_handshake_thread = kthread_run(epl_client_fsi_handshake, NULL, "fsi-hs");
if (IS_ERR(fsi_handshake_thread))
return PTR_ERR(fsi_handshake_thread);
return 0;
}
static SIMPLE_DEV_PM_OPS(epl_client_pm, epl_client_suspend, epl_client_resume);
static const struct of_device_id epl_client_dt_match[] = {
{ .compatible = "nvidia,tegra234-epl-client"},
{}
};
MODULE_DEVICE_TABLE(of, epl_client_dt_match);
/* File operations */
static const struct file_operations epl_driver_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = device_file_ioctl,
};
static int epl_register_device(void)
{
int result = 0;
struct class *dev_class;
result = register_chrdev(0, device_name, &epl_driver_fops);
if (result < 0) {
pr_err("%s> register_chrdev code = %i\n", device_name, result);
return result;
}
device_file_major_number = result;
dev_class = class_create(device_name);
if (dev_class == NULL) {
pr_err("%s> Could not create class for device\n", device_name);
goto class_fail;
}
if ((device_create(dev_class, NULL,
MKDEV(device_file_major_number, 0),
NULL, device_name)) == NULL) {
pr_err("%s> Could not create device node\n", device_name);
goto device_failure;
}
return 0;
device_failure:
class_destroy(dev_class);
class_fail:
unregister_chrdev(device_file_major_number, device_name);
return -1;
}
static void epl_unregister_device(void)
{
if (device_file_major_number != 0)
unregister_chrdev(device_file_major_number, device_name);
}
static int epl_client_probe(struct platform_device *pdev)
{
int ret = 0;
struct device *dev = &pdev->dev;
const struct device_node *np = dev->of_node;
int iterator = 0;
char name[32] = "client-misc-sw-generic-err";
mutex_lock(&hs_state_mutex);
hs_state = HANDSHAKE_PENDING;
mutex_unlock(&hs_state_mutex);
epl_register_device();
ret = tegra_hsp_mb_init(dev);
pdev_local = pdev;
for (iterator = 0; iterator < NUM_SW_GENERIC_ERR; iterator++) {
name[26] = (char)(iterator+48U);
name[27] = '\0';
if (of_property_read_string(np, name, &miscerr_cfg[iterator].dev_configured) == 0) {
pr_info("Misc Sw Generic Err #%d configured to client %s\n",
iterator, miscerr_cfg[iterator].dev_configured);
/* Mapping registers to process address space */
miscerr_cfg[iterator].err_code_va =
devm_platform_ioremap_resource(pdev, (iterator * 2));
miscerr_cfg[iterator].err_assert_va =
devm_platform_ioremap_resource(pdev, (iterator * 2) + 1);
if (IS_ERR(miscerr_cfg[iterator].err_code_va) ||
IS_ERR(miscerr_cfg[iterator].err_assert_va)) {
isAddrMappOk = false;
ret = -1;
dev_err(&pdev->dev, "error in mapping misc err register for err #%d\n",
iterator);
}
} else {
pr_info("Misc Sw Generic Err %d not configured for any client\n", iterator);
}
}
mission_err_status_va = devm_platform_ioremap_resource(pdev, NUM_SW_GENERIC_ERR * 2);
if (IS_ERR(mission_err_status_va)) {
isAddrMappOk = false;
dev_err(&pdev->dev, "error in mapping mission error status register\n");
return PTR_ERR(mission_err_status_va);
}
if (ret == 0) {
fsi_handshake_thread = kthread_run(epl_client_fsi_handshake, NULL, "fsi-hs");
if (IS_ERR(fsi_handshake_thread))
return PTR_ERR(fsi_handshake_thread);
}
return ret;
}
static int epl_client_remove(struct platform_device *pdev)
{
epl_unregister_device();
return 0;
}
static struct platform_driver epl_client = {
.driver = {
.name = "epl_client",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.of_match_table = of_match_ptr(epl_client_dt_match),
.pm = pm_ptr(&epl_client_pm),
},
.probe = epl_client_probe,
.remove = epl_client_remove,
};
module_platform_driver(epl_client);
MODULE_DESCRIPTION("tegra: Error Propagation Library driver");
MODULE_AUTHOR("Prashant Shaw <pshaw@nvidia.com>");
MODULE_LICENSE("GPL v2");
+103
View File
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#ifndef _TEGRA_EPL_H_
#define _TEGRA_EPL_H_
#include <uapi/linux/tegra-epl.h>
/**
* @brief Error report frame
*/
struct epl_error_report_frame {
/* Error code indicates error reported by corresponding reporter_id */
uint32_t error_code;
/* Extra information for SEH to understand error */
uint32_t error_attribute;
/* LSB 32-bit TSC counter when error is detected */
uint32_t timestamp;
/* Indicates source of error */
uint16_t reporter_id;
};
#ifdef CONFIG_TEGRA_EPL
/**
* @brief API to check if SW error can be reported via Misc EC
* by reading and checking Misc EC error status register value.
*
* @param[in] dev pointer to the device structure for the kernel driver
* from where API is called.
* @param[in] err_number Generic SW error number for which status needs to
* enquired - [0 to 4].
* @param[out] status out param updated by API as follows:
* true - SW error can be reported
* false - SW error can not be reported because previous error
* is still active. Client needs to retry later.
*
* @returns
* 0 (success)
* -EINVAL (On invalid arguments)
* -ENODEV (On device driver not loaded or Misc EC not configured)
* -EACCESS (On client not allowed to report error via given Misc EC)
* -EAGAIN (On Misc EC busy, client should retry)
*/
int epl_get_misc_ec_err_status(struct device *dev, uint8_t err_number, bool *status);
/**
* @brief API to report SW error to FSI using Misc Generic SW error lines connected to
* the Misc error collator.
*
* @param[in] dev pointer to the device structure for the kernel driver
* from where API is called.
* @param[in] err_number Generic SW error number through which error
* needs to be reported.
* @param[in] sw_error_code Client Defined Error Code, which will be
* forwarded to the application on FSI.
*
* @returns
* 0 (success)
* -EINVAL (On invalid arguments)
* -ENODEV (On device driver not loaded or Misc EC not configured)
* -EACCESS (On client not allowed to report error via given Misc EC)
* -EAGAIN (On Misc EC busy, client should retry)
*/
int epl_report_misc_ec_error(struct device *dev, uint8_t err_number, uint32_t sw_error_code);
/**
* @brief API to report SW error via TOP2 HSP
*
* @param[in] error_report Error frame to be reported
*
* @return
* 0 (Success)
* -ENODEV (On device driver not loaded or not configured)
* -ETIME (On timeout)
*/
int epl_report_error(struct epl_error_report_frame error_report);
#else
static inline
int epl_get_misc_ec_err_status(struct device *dev, uint8_t err_number, bool *status)
{
return -ENODEV;
}
static inline
int epl_report_misc_ec_error(struct device *dev, uint8_t err_number, uint32_t sw_error_code)
{
return -ENODEV;
}
static inline
int epl_report_error(struct epl_error_report_frame error_report)
{
return -ENODEV;
}
#endif /* CONFIG_TEGRA_EPL */
#endif /* _TEGRA_EPL_H_ */
+14
View File
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note)
*
* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#ifndef _UAPI_TEGRA_EPL_H_
#define _UAPI_TEGRA_EPL_H_
#include <linux/ioctl.h>
/* ioctl call macros */
#define EPL_REPORT_ERROR_CMD _IOWR('q', 1, uint8_t *)
#endif /* _UAPI_TEGRA_EPL_H_ */