From e821bb6f75fdc1eb6bdb3fcd2211a65bb8de6f5f Mon Sep 17 00:00:00 2001 From: Rahul Bedarkar Date: Thu, 27 Oct 2022 09:09:20 +0000 Subject: [PATCH] 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 Reviewed-by: Abhilash G Tested-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/Kconfig | 2 + drivers/platform/Makefile | 1 + drivers/platform/tegra/Kconfig | 30 +++ drivers/platform/tegra/Makefile | 2 + drivers/platform/tegra/tegra-epl.c | 393 +++++++++++++++++++++++++++++ include/linux/tegra-epl.h | 103 ++++++++ include/uapi/linux/tegra-epl.h | 14 + 8 files changed, 553 insertions(+) create mode 100644 drivers/platform/tegra/Kconfig create mode 100644 drivers/platform/tegra/Makefile create mode 100644 drivers/platform/tegra/tegra-epl.c create mode 100644 include/linux/tegra-epl.h create mode 100644 include/uapi/linux/tegra-epl.h diff --git a/MAINTAINERS b/MAINTAINERS index f639e6819624..ffbebe5c5318 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21606,6 +21606,14 @@ M: Jon Hunter S: Supported F: drivers/dma/tegra* +TEGRA EPL DRIVER +M: Rahul Bedarkar +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 R: Dmitry Osipenko diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 868b20361769..da1ad2bd3811 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -14,3 +14,5 @@ source "drivers/platform/olpc/Kconfig" source "drivers/platform/surface/Kconfig" source "drivers/platform/x86/Kconfig" + +source "drivers/platform/tegra/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 41640172975a..e9ab72ea39df 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -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/ diff --git a/drivers/platform/tegra/Kconfig b/drivers/platform/tegra/Kconfig new file mode 100644 index 000000000000..404281dc1e6e --- /dev/null +++ b/drivers/platform/tegra/Kconfig @@ -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 diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile new file mode 100644 index 000000000000..d20e79503472 --- /dev/null +++ b/drivers/platform/tegra/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_TEGRA_EPL) += tegra-epl.o diff --git a/drivers/platform/tegra/tegra-epl.c b/drivers/platform/tegra/tegra-epl.c new file mode 100644 index 000000000000..b2d3c2e10075 --- /dev/null +++ b/drivers/platform/tegra/tegra-epl.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/tegra-epl.h b/include/linux/tegra-epl.h new file mode 100644 index 000000000000..cd0447be1f74 --- /dev/null +++ b/include/linux/tegra-epl.h @@ -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 + +/** + * @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_ */ diff --git a/include/uapi/linux/tegra-epl.h b/include/uapi/linux/tegra-epl.h new file mode 100644 index 000000000000..2418f81aa8d6 --- /dev/null +++ b/include/uapi/linux/tegra-epl.h @@ -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 + +/* ioctl call macros */ +#define EPL_REPORT_ERROR_CMD _IOWR('q', 1, uint8_t *) + +#endif /* _UAPI_TEGRA_EPL_H_ */