summaryrefslogtreecommitdiff
path: root/drivers/soc
diff options
context:
space:
mode:
authorFranck LENORMAND <franck.lenormand@nxp.com>2019-12-05 16:23:37 +0100
committerFranck LENORMAND <franck.lenormand@nxp.com>2020-03-13 17:11:58 +0100
commit7c907f8b54f516ff8a8505f1f6d8ea2927c35c7a (patch)
tree1a409744f560b1855a121c4329cb41a31a375e94 /drivers/soc
parente0ef5fc65d85b74b8bc9354030186c0e23c1878d (diff)
SSI-87: soc: imx: secvio: Add support for SNVS secvio and tamper via SCFW
The driver register an IRQ handle to SCU for security violation interrupt. When an interruption is fired, the driver inform the user. Signed-off-by: Franck LENORMAND <franck.lenormand@nxp.com>
Diffstat (limited to 'drivers/soc')
-rw-r--r--drivers/soc/imx/Kconfig10
-rw-r--r--drivers/soc/imx/Makefile1
-rw-r--r--drivers/soc/imx/secvio/Makefile2
-rw-r--r--drivers/soc/imx/secvio/imx-secvio-debugfs.c276
-rw-r--r--drivers/soc/imx/secvio/imx-secvio-sc-int.h70
-rw-r--r--drivers/soc/imx/secvio/imx-secvio-sc.c656
6 files changed, 1015 insertions, 0 deletions
diff --git a/drivers/soc/imx/Kconfig b/drivers/soc/imx/Kconfig
index 21c2a9484a88..10822d39b2a5 100644
--- a/drivers/soc/imx/Kconfig
+++ b/drivers/soc/imx/Kconfig
@@ -27,4 +27,14 @@ config IMX8M_BUSFREQ
bool "i.MX8M busfreq"
depends on ARCH_MXC && ARM64
+config SECVIO_SC
+ tristate "NXP SC secvio support"
+ depends on IMX_SCU
+ help
+ If you say yes here you get support for the NXP SNVS security
+ violation module. It includes the possibility to read information
+ related to security violations and tampers. It also gives the
+ possibility to register user callbacks when a security violation
+ occurs.
+
endmenu
diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile
index 7d5a129b4d35..6a177d9d8cd4 100644
--- a/drivers/soc/imx/Makefile
+++ b/drivers/soc/imx/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_ARM64) += soc-imx8.o
obj-$(CONFIG_IMX_SCU_SOC) += soc-imx-scu.o
obj-$(CONFIG_IMX8M_PM_DOMAINS) += imx8m_pm_domains.o
obj-y += mu/
+obj-${CONFIG_SECVIO_SC} += secvio/
diff --git a/drivers/soc/imx/secvio/Makefile b/drivers/soc/imx/secvio/Makefile
new file mode 100644
index 000000000000..55ef1c044009
--- /dev/null
+++ b/drivers/soc/imx/secvio/Makefile
@@ -0,0 +1,2 @@
+obj-y += imx-secvio-sc.o
+obj-$(CONFIG_DEBUG_FS) += imx-secvio-debugfs.o
diff --git a/drivers/soc/imx/secvio/imx-secvio-debugfs.c b/drivers/soc/imx/secvio/imx-secvio-debugfs.c
new file mode 100644
index 000000000000..786c943829da
--- /dev/null
+++ b/drivers/soc/imx/secvio/imx-secvio-debugfs.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP
+ *
+ */
+
+/*
+ * The module exposes 3 files in debugfs:
+ * - secvio/info:
+ * * Read: It returns the value of the fuses and SNVS registers which are
+ * readable and related to secvio and tampers
+ * * Write: A write of the format "<hex id> [<hex value 0> <hex value 1>
+ * <hex value 2> <hex value 3> <hex value 4>](<nb values>)"
+ * will write the SNVS register having the provided id with the
+ * values provided (cf SECO ducumentation)
+ * - secvio/enable: State of the IRQ
+ * - secvio/check: Check the state of the security violation and tampers
+ * and calls notifier
+ * - secvio/clear: Clear the state of all secvio and tampers
+ */
+
+/* Includes */
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/nvmem-consumer.h>
+
+#include <linux/firmware/imx/svc/misc.h>
+#include <linux/firmware/imx/svc/seco.h>
+
+#include <soc/imx/imx-secvio-sc.h>
+#include "imx-secvio-sc-int.h"
+
+int fuse_reader(struct device *dev, u32 id, u32 *value, u8 mul)
+{
+ struct imx_secvio_sc_data *data = dev_get_drvdata(dev);
+ u32 size_to_read = mul * sizeof(u32);
+ int ret;
+
+ ret = nvmem_device_read(data->nvmem, id, size_to_read, value);
+ if (ret < 0) {
+ dev_err(data->dev, "Failed to read fuse %d: %d\n", id, ret);
+ return ret;
+ }
+
+ if (ret != size_to_read) {
+ dev_err(data->dev, "Read only %d instead of %d\n", ret,
+ size_to_read);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+int snvs_reader(struct device *dev, u32 id, u32 *value, u8 mul)
+{
+ int ret;
+ u32 *v1, *v2, *v3, *v4, *v5;
+
+ v1 = v2 = v3 = v4 = v5 = NULL;
+
+ switch (mul) {
+ case 5:
+ v5 = &value[4];
+ case 4:
+ v4 = &value[3];
+ case 3:
+ v3 = &value[2];
+ case 2:
+ v2 = &value[1];
+ case 1:
+ v1 = &value[0];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = call_secvio_config(dev, id, SECVIO_CONFIG_READ, v1, v2, v3, v4,
+ v5, mul);
+ if (ret < 0)
+ dev_err(dev, "Failed to read snvs reg %d: %d\n", id, ret);
+
+ return ret;
+}
+
+int snvs_dgo_reader(struct device *dev, u32 id, u32 *value, u8 mul)
+{
+ struct imx_secvio_sc_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ if (mul != 1)
+ return -EINVAL;
+
+ ret = imx_sc_seco_secvio_dgo_config(data->ipc_handle, id,
+ SECVIO_CONFIG_READ, value);
+ if (ret)
+ dev_err(dev, "Failed to read snvs dgo reg %d: %d\n", id, ret);
+
+ return ret;
+}
+
+static const struct imx_secvio_info_entry {
+ int (*reader)(struct device *dev, u32 id, u32 *value, u8 mul);
+ const char *type;
+ const char *name;
+ u32 id;
+ u8 mul;
+} gs_imx_secvio_info_list[] = {
+ {fuse_reader, "fuse", "trim", 30, 1},
+ {fuse_reader, "fuse", "trim2", 31, 1},
+ {fuse_reader, "fuse", "ctrim1", 260, 1},
+ {fuse_reader, "fuse", "ctrim2", 261, 1},
+ {fuse_reader, "fuse", "ctrim3", 262, 1},
+ {fuse_reader, "fuse", "ctrim4", 263, 1},
+ {fuse_reader, "fuse", "OSC_CAP", 768, 1},
+
+ {snvs_reader, "snvs", "HPLR", 0x0, 1},
+ {snvs_reader, "snvs", "LPLR", 0x34, 1},
+ {snvs_reader, "snvs", "HPSICR", 0xc, 1},
+ {snvs_reader, "snvs", "HPSVCR", 0x10, 1},
+ {snvs_reader, "snvs", "HPSVS", 0x18, 1},
+ {snvs_reader, "snvs", "LPSVC", 0x40, 1},
+ {snvs_reader, "snvs", "LPTDC", 0x48, 2},
+ {snvs_reader, "snvs", "LPSR", 0x4c, 1},
+ {snvs_reader, "snvs", "LPTDS", 0xa4, 1},
+ {snvs_reader, "snvs", "LPTGFC", 0x44, 3},
+ {snvs_reader, "snvs", "LPATCTL", 0xe0, 1},
+ {snvs_reader, "snvs", "LPATCLK", 0xe4, 1},
+ {snvs_reader, "snvs", "LPATRC1", 0xe8, 2},
+ {snvs_reader, "snvs", "LPMKC", 0x3c, 1},
+ {snvs_reader, "snvs", "LPSMC", 0x5c, 2},
+ {snvs_reader, "snvs", "LPPGD", 0x64, 1},
+ {snvs_reader, "snvs", "HPVID", 0xf8, 2},
+
+ {snvs_dgo_reader, "dgo", "Offset", 0x0, 1},
+ {snvs_dgo_reader, "dgo", "PUP/PD", 0x10, 1},
+ {snvs_dgo_reader, "dgo", "Anatest", 0x20, 1},
+ {snvs_dgo_reader, "dgo", "T trim", 0x30, 1},
+ {snvs_dgo_reader, "dgo", "Misc", 0x40, 1},
+ {snvs_dgo_reader, "dgo", "Vmon", 0x50, 1},
+};
+
+struct imx_secvio_sc_info_seq_data {
+ struct device *dev;
+ const struct imx_secvio_info_entry *list;
+ int size;
+};
+
+static void * imx_secvio_sc_info_seq_start(struct seq_file *m, loff_t *pos)
+{
+ struct imx_secvio_sc_info_seq_data *data = m->private;
+
+ /* Check we are not out of bound */
+ if (*pos >= data->size) {
+ return NULL;
+ }
+
+ return (void *)pos;
+}
+
+static void * imx_secvio_sc_info_seq_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ /* Increment the counter */
+ ++*pos;
+
+ /* call the start function which will check the index */
+ return imx_secvio_sc_info_seq_start(m, pos);
+}
+
+static void imx_secvio_sc_info_seq_stop(struct seq_file *m, void *v)
+{
+}
+
+static int imx_secvio_sc_info_seq_show(struct seq_file *m, void *v)
+{
+ struct imx_secvio_sc_info_seq_data *data = m->private;
+ const struct imx_secvio_info_entry *e;
+ int ret;
+ u32 vals[5];
+ int idx;
+
+ idx = *(loff_t *)v;
+ e = &data->list[idx];
+
+ /* Read the values */
+ ret = e->reader(data->dev, e->id, (u32 *)&vals, e->mul);
+ if (ret) {
+ dev_err(data->dev, "Fail to read %s %s (idx %d)\n", e->type,
+ e->name, e->id);
+ return 0;
+ }
+
+ seq_printf(m, "%5s/%-10s(%.3d):", e->type, e->name, e->id);
+
+ /* Loop over the values */
+ for (idx = 0; idx < e->mul; idx++)
+ seq_printf(m, " %.8x", vals[idx]);
+
+ seq_printf(m, "\n");
+
+ return 0;
+}
+
+static const struct seq_operations imx_secvio_sc_info_seq_ops = {
+ .start = imx_secvio_sc_info_seq_start,
+ .next = imx_secvio_sc_info_seq_next,
+ .stop = imx_secvio_sc_info_seq_stop,
+ .show = imx_secvio_sc_info_seq_show,
+};
+
+static int imx_secvio_sc_info_open(struct inode *inode, struct file *file)
+{
+ struct imx_secvio_sc_info_seq_data *data;
+
+ data = __seq_open_private(file, &imx_secvio_sc_info_seq_ops, sizeof(*data));
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = inode->i_private;
+ data->list = gs_imx_secvio_info_list;
+ data->size = ARRAY_SIZE(gs_imx_secvio_info_list);
+
+ return 0;
+}
+
+static const struct file_operations imx_secvio_sc_info_ops = {
+ .owner = THIS_MODULE,
+ .open = imx_secvio_sc_info_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release_private,
+};
+
+static void if_debugfs_remove_recursive(void *dentry)
+{
+ debugfs_remove_recursive(dentry);
+}
+
+int imx_secvio_sc_debugfs(struct device *dev)
+{
+ struct imx_secvio_sc_data *data = dev_get_drvdata(dev);
+ struct dentry *dir;
+ int ret = 0;
+
+ /* Create a folder */
+ dir = debugfs_create_dir(dev_name(dev), NULL);
+ if (IS_ERR(dir)) {
+ dev_err(dev, "Failed to create dfs dir\n");
+ ret = PTR_ERR(dir);
+ goto exit;
+ }
+ data->dfs = dir;
+
+ ret = devm_add_action(dev, if_debugfs_remove_recursive, data->dfs);
+ if (ret) {
+ dev_err(dev, "Failed to add managed action to disable IRQ\n");
+ goto remove_fs;
+ }
+
+ /* Create the file to read info and write to reg */
+ dir = debugfs_create_file("info", 0x666, data->dfs, dev,
+ &imx_secvio_sc_info_ops);
+ if (IS_ERR(dir)) {
+ dev_err(dev, "Failed to add info to debugfs\n");
+ ret = PTR_ERR(dir);
+ goto exit;
+ }
+
+exit:
+ return ret;
+
+remove_fs:
+ debugfs_remove_recursive(data->dfs);
+ goto exit;
+}
diff --git a/drivers/soc/imx/secvio/imx-secvio-sc-int.h b/drivers/soc/imx/secvio/imx-secvio-sc-int.h
new file mode 100644
index 000000000000..af3a98e0503c
--- /dev/null
+++ b/drivers/soc/imx/secvio/imx-secvio-sc-int.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2019 NXP
+ */
+
+#ifndef SECVIO_SC_H
+#define SECVIO_SC_H
+
+/* Includes */
+#include <linux/kernel.h>
+#include <linux/notifier.h>
+#include <linux/semaphore.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/miscdevice.h>
+
+/* Access for sc_seco_secvio_config API */
+#define SECVIO_CONFIG_READ 0
+#define SECVIO_CONFIG_WRITE 1
+
+/* Internal Structure */
+struct imx_secvio_sc_data {
+ struct device *dev;
+
+ struct imx_sc_ipc *ipc_handle;
+
+ struct notifier_block irq_nb;
+ struct notifier_block report_nb;
+
+ struct nvmem_device *nvmem;
+
+ struct miscdevice miscdev;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dfs;
+#endif
+
+ u32 version;
+};
+
+/* Function declarations */
+extern
+int call_secvio_config(struct device *dev, u8 id, u8 access, u32 *data0,
+ u32 *data1, u32 *data2, u32 *data3, u32 *data4, u8 size);
+
+extern
+int int_imx_secvio_sc_get_state(struct device *dev,
+ struct secvio_sc_notifier_info *info);
+
+extern
+int int_imx_secvio_sc_clear_state(struct device *dev, u32 hpsvs, u32 lps,
+ u32 lptds);
+
+extern
+int int_imx_secvio_sc_enable_irq(struct device *dev);
+
+extern
+int int_imx_secvio_sc_disable_irq(struct device *dev);
+
+#ifdef CONFIG_DEBUG_FS
+extern
+int imx_secvio_sc_debugfs(struct device *dev);
+#else
+static inline
+int imx_secvio_sc_debugfs(struct device *dev)
+{
+ return 0;
+}
+#endif /* CONFIG_DEBUG_FS */
+
+#endif /* SECVIO_SC_H */
diff --git a/drivers/soc/imx/secvio/imx-secvio-sc.c b/drivers/soc/imx/secvio/imx-secvio-sc.c
new file mode 100644
index 000000000000..754acd582c1a
--- /dev/null
+++ b/drivers/soc/imx/secvio/imx-secvio-sc.c
@@ -0,0 +1,656 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP
+ *
+ */
+
+/*
+ * The i.MX8QXP SoC contains the Secure Non-Volatile Storage (SNVS) block. This
+ * block can detect specific hardware attacks. Due to the presence of the SECO,
+ * this block can only be accessible using the SCFW API.
+ *
+ * This module interact with the SCU which relay request to/from the SNVS block
+ * to detect if security violation occurred.
+ *
+ * The module exports an API to add processing when a SV is detected:
+ * - register_imx_secvio_sc_notifier
+ * - unregister_imx_secvio_sc_notifier
+ * - imx_secvio_sc_check_state
+ * - int_imx_secvio_sc_clear_state
+ * - imx_secvio_sc_enable_irq
+ * - imx_secvio_sc_disable_irq
+ */
+
+/* Includes */
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/notifier.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/miscdevice.h>
+
+#include <linux/firmware/imx/ipc.h>
+#include <linux/firmware/imx/sci.h>
+#include <linux/firmware/imx/svc/seco.h>
+#include <linux/firmware/imx/svc/rm.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+
+#include <soc/imx/imx-secvio-sc.h>
+#include "imx-secvio-sc-int.h"
+
+/* Definitions */
+
+/* Reference on the driver_device */
+static struct device *gs_imx_secvio_sc_dev;
+
+/* Register IDs for sc_seco_secvio_config API */
+#define HPSVS_ID 0x18
+#define LPS_ID 0x4c
+#define LPTDS_ID 0xa4
+#define HPVIDR_ID 0xf8
+
+#define SECO_MINOR_VERSION_SUPPORT_SECVIO_TAMPER 0x53
+#define SECO_VERSION_MINOR_MASK GENMASK(15, 0)
+
+/* Notifier list for new CB */
+static BLOCKING_NOTIFIER_HEAD(imx_secvio_sc_notifier_chain);
+
+int register_imx_secvio_sc_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&imx_secvio_sc_notifier_chain,
+ nb);
+}
+EXPORT_SYMBOL(register_imx_secvio_sc_notifier);
+
+int unregister_imx_secvio_sc_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&imx_secvio_sc_notifier_chain,
+ nb);
+}
+EXPORT_SYMBOL(unregister_imx_secvio_sc_notifier);
+
+static void if_imx_scu_irq_register_notifier(void *nb)
+{
+ imx_scu_irq_register_notifier(nb);
+}
+
+static void if_unregister_imx_secvio_sc_notifier(void *nb)
+{
+ unregister_imx_secvio_sc_notifier(nb);
+}
+
+static
+int imx_secvio_sc_notifier_call_chain(struct secvio_sc_notifier_info *info)
+{
+ return blocking_notifier_call_chain(&imx_secvio_sc_notifier_chain, 0,
+ (void *)info);
+}
+
+int int_imx_secvio_sc_get_state(struct device *dev,
+ struct secvio_sc_notifier_info *info)
+{
+ struct secvio_sc_notifier_info _info = {0};
+ struct secvio_sc_notifier_info *p_info;
+ int ret = 0, ret2 = 0;
+
+ p_info = info ? info : &_info;
+
+ /* Read secvio status */
+ ret = call_secvio_config(dev, HPSVS_ID, SECVIO_CONFIG_READ,
+ &p_info->hpsvs, NULL, NULL, NULL, NULL, 1);
+ if (ret) {
+ ret2 = ret;
+ dev_err(dev, "Cannot read secvio status: %d\n", ret);
+ }
+ p_info->hpsvs &= HPSVS__ALL_SV__MASK;
+
+ /* Read tampers status */
+ ret = call_secvio_config(dev, LPS_ID, SECVIO_CONFIG_READ,
+ &p_info->lps, NULL, NULL, NULL, NULL, 1);
+ if (ret) {
+ ret2 = ret;
+ dev_err(dev, "Cannot read tamper 1 status: %d\n", ret);
+ }
+ p_info->lps &= LPS__ALL_TP__MASK;
+
+ ret = call_secvio_config(dev, LPTDS_ID, SECVIO_CONFIG_READ,
+ &p_info->lptds, NULL, NULL, NULL, NULL, 1);
+ if (ret) {
+ ret2 = ret;
+ dev_err(dev, "Cannot read tamper 2 status: %d\n", ret);
+ }
+ p_info->lptds &= LPTDS__ALL_TP__MASK;
+
+ dev_dbg(dev, "Status: %.8x, %.8x, %.8x\n", p_info->hpsvs,
+ p_info->lps, p_info->lptds);
+
+ return ret2;
+}
+
+inline int imx_secvio_sc_get_state(struct secvio_sc_notifier_info *info)
+{
+ return int_imx_secvio_sc_get_state(gs_imx_secvio_sc_dev, info);
+}
+EXPORT_SYMBOL(imx_secvio_sc_get_state);
+
+int int_imx_secvio_sc_check_state(struct device *dev)
+{
+ struct secvio_sc_notifier_info info = {0};
+ int ret = 0;
+
+ ret = int_imx_secvio_sc_get_state(dev, &info);
+ if (ret) {
+ dev_err(dev, "Failed to get secvio state\n");
+ goto exit;
+ }
+
+ /* Call chain of CB registered to this module if status detected */
+ if (info.hpsvs || info.lps || info.lptds)
+ if (imx_secvio_sc_notifier_call_chain(&info))
+ dev_warn(dev,
+ "Issues when calling the notifier chain\n");
+
+exit:
+ return ret;
+}
+
+inline int imx_secvio_sc_check_state(void)
+{
+ return int_imx_secvio_sc_check_state(gs_imx_secvio_sc_dev);
+}
+EXPORT_SYMBOL(imx_secvio_sc_check_state);
+
+static int imx_secvio_sc_notify(struct notifier_block *nb,
+ unsigned long event, void *group)
+{
+ struct imx_secvio_sc_data *data =
+ container_of(nb, struct imx_secvio_sc_data,
+ irq_nb);
+ struct device *dev = data->dev;
+ int ret = 0;
+
+ /* Filter event for us */
+ if (!((event & IMX_SC_IRQ_SECVIO) &&
+ (*(u8 *)group == IMX_SC_IRQ_GROUP_WAKE)))
+ goto exit;
+
+ dev_warn(dev, "secvio security violation detected\n");
+
+ ret = int_imx_secvio_sc_check_state(dev);
+
+ /* Re-enable interrupt */
+ ret = int_imx_secvio_sc_enable_irq(dev);
+ if (ret)
+ dev_err(dev, "Failed to enable IRQ\n");
+
+exit:
+ return ret;
+}
+
+int int_imx_secvio_sc_clear_state(struct device *dev, u32 hpsvs, u32 lps,
+ u32 lptds)
+{
+ int ret = 0;
+
+ if (!dev)
+ return -EINVAL;
+
+ ret = call_secvio_config(dev, HPSVS_ID, SECVIO_CONFIG_WRITE, &hpsvs,
+ NULL, NULL, NULL, NULL, 1);
+ if (ret) {
+ dev_err(dev, "Cannot clear secvio status: %d\n", ret);
+ goto exit;
+ }
+
+ ret = call_secvio_config(dev, LPS_ID, SECVIO_CONFIG_WRITE, &lps, NULL,
+ NULL, NULL, NULL, 1);
+ if (ret) {
+ dev_err(dev, "Cannot clear tamper 1 status: %d\n", ret);
+ goto exit;
+ }
+
+ ret = call_secvio_config(dev, LPTDS_ID, SECVIO_CONFIG_WRITE, &lptds,
+ NULL, NULL, NULL, NULL, 1);
+ if (ret) {
+ dev_err(dev, "Cannot clear tamper 2 status: %d\n", ret);
+ goto exit;
+ }
+
+exit:
+ return ret;
+}
+
+inline int imx_secvio_sc_clear_state(u32 hpsvs, u32 lps, u32 lptds)
+{
+ return int_imx_secvio_sc_clear_state(gs_imx_secvio_sc_dev, hpsvs, lps,
+ lptds);
+}
+EXPORT_SYMBOL(imx_secvio_sc_clear_state);
+
+static int report_to_user_notify(struct notifier_block *nb,
+ unsigned long status, void *notif_info)
+{
+ struct secvio_sc_notifier_info *info = notif_info;
+ struct imx_secvio_sc_data *data =
+ container_of(nb, struct imx_secvio_sc_data,
+ report_nb);
+ struct device *dev = data->dev;
+
+ /* Information about the security violation */
+ if (info->hpsvs & HPSVS__LP_SEC_VIO__MASK)
+ dev_info(dev, "SNVS secvio: LPSV\n");
+ if (info->hpsvs & HPSVS__SW_LPSV__MASK)
+ dev_info(dev, "SNVS secvio: SW LPSV\n");
+ if (info->hpsvs & HPSVS__SW_FSV__MASK)
+ dev_info(dev, "SNVS secvio: SW FSV\n");
+ if (info->hpsvs & HPSVS__SW_SV__MASK)
+ dev_info(dev, "SNVS secvio: SW SV\n");
+ if (info->hpsvs & HPSVS__SV5__MASK)
+ dev_info(dev, "SNVS secvio: SV 5\n");
+ if (info->hpsvs & HPSVS__SV4__MASK)
+ dev_info(dev, "SNVS secvio: SV 4\n");
+ if (info->hpsvs & HPSVS__SV3__MASK)
+ dev_info(dev, "SNVS secvio: SV 3\n");
+ if (info->hpsvs & HPSVS__SV2__MASK)
+ dev_info(dev, "SNVS secvio: SV 2\n");
+ if (info->hpsvs & HPSVS__SV1__MASK)
+ dev_info(dev, "SNVS secvio: SV 1\n");
+ if (info->hpsvs & HPSVS__SV0__MASK)
+ dev_info(dev, "SNVS secvio: SV 0\n");
+
+ /* Information about the tampers */
+ if (info->lps & LPS__ESVD__MASK)
+ dev_info(dev, "SNVS tamper: External SV\n");
+ if (info->lps & LPS__ET2D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 2\n");
+ if (info->lps & LPS__ET1D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 1\n");
+ if (info->lps & LPS__WMT2D__MASK)
+ dev_info(dev, "SNVS tamper: Wire Mesh 2\n");
+ if (info->lps & LPS__WMT1D__MASK)
+ dev_info(dev, "SNVS tamper: Wire Mesh 1\n");
+ if (info->lps & LPS__VTD__MASK)
+ dev_info(dev, "SNVS tamper: Voltage\n");
+ if (info->lps & LPS__TTD__MASK)
+ dev_info(dev, "SNVS tamper: Temperature\n");
+ if (info->lps & LPS__CTD__MASK)
+ dev_info(dev, "SNVS tamper: Clock\n");
+ if (info->lps & LPS__PGD__MASK)
+ dev_info(dev, "SNVS tamper: Power Glitch\n");
+ if (info->lps & LPS__MCR__MASK)
+ dev_info(dev, "SNVS tamper: Monotonic Counter rollover\n");
+ if (info->lps & LPS__SRTCR__MASK)
+ dev_info(dev, "SNVS tamper: Secure RTC rollover\n");
+ if (info->lps & LPS__LPTA__MASK)
+ dev_info(dev, "SNVS tamper: Time alarm\n");
+
+ if (info->lptds & LPTDS__ET10D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 10\n");
+ if (info->lptds & LPTDS__ET9D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 9\n");
+ if (info->lptds & LPTDS__ET8D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 8\n");
+ if (info->lptds & LPTDS__ET7D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 7\n");
+ if (info->lptds & LPTDS__ET6D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 6\n");
+ if (info->lptds & LPTDS__ET5D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 5\n");
+ if (info->lptds & LPTDS__ET4D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 4\n");
+ if (info->lptds & LPTDS__ET3D__MASK)
+ dev_info(dev, "SNVS tamper: Tamper 3\n");
+
+ return 0;
+}
+
+int call_secvio_config(struct device *dev, u8 id, u8 access, u32 *data0,
+ u32 *data1, u32 *data2, u32 *data3, u32 *data4, u8 size)
+{
+ int ret = 0;
+ struct imx_secvio_sc_data *data;
+
+ if (!dev)
+ return -EINVAL;
+
+ data = dev_get_drvdata(dev);
+
+ ret = imx_sc_seco_secvio_config(data->ipc_handle, id, access, data0,
+ data1, data2, data3, data4, size);
+ if (ret)
+ dev_err(dev, "Fail %s secvio config %d",
+ ((access) ? "write" : "read"), ret);
+
+ return ret;
+}
+
+int int_imx_secvio_sc_enable_irq(struct device *dev)
+{
+ int ret = 0, ret2;
+ u32 irq_status;
+ struct imx_secvio_sc_data *data;
+
+ if (!dev)
+ return -EINVAL;
+
+ data = dev_get_drvdata(dev);
+
+ /* Enable the IRQ */
+ ret = imx_scu_irq_group_enable(IMX_SC_IRQ_GROUP_WAKE, IMX_SC_IRQ_SECVIO,
+ true);
+ if (ret) {
+ dev_err(dev, "Cannot enable SCU IRQ: %d\n", ret);
+ goto exit;
+ }
+
+ /* Enable interrupt */
+ ret = imx_sc_seco_secvio_enable(data->ipc_handle);
+ if (ret) {
+ dev_err(dev, "Cannot enable SNVS irq: %d\n", ret);
+ goto exit;
+ };
+
+ /* Unmask interrupt */
+ ret = imx_scu_irq_get_status(IMX_SC_IRQ_GROUP_WAKE, &irq_status);
+ if (ret) {
+ dev_err(dev, "Cannot unmask irq: %d\n", ret);
+ goto exit;
+ };
+
+exit:
+ if (ret) {
+ ret2 = int_imx_secvio_sc_disable_irq(dev);
+ if (ret2)
+ dev_warn(dev, "Failed to disable the IRQ\n");
+ }
+
+ return ret;
+}
+
+int int_imx_secvio_sc_disable_irq(struct device *dev)
+{
+ int ret = 0;
+ struct imx_secvio_sc_data *data;
+
+ if (!dev)
+ return -EINVAL;
+
+ data = dev_get_drvdata(dev);
+
+ /* Disable the IRQ */
+ ret = imx_scu_irq_group_enable(IMX_SC_IRQ_GROUP_WAKE, IMX_SC_IRQ_SECVIO,
+ false);
+ if (ret) {
+ dev_err(dev, "Cannot disable SCU IRQ: %d\n", ret);
+ goto exit;
+ }
+
+exit:
+ return ret;
+}
+
+static void if_imx_secvio_sc_disable_irq(void *dev)
+{
+ int_imx_secvio_sc_disable_irq(dev);
+}
+
+static int imx_secvio_sc_open(struct inode *node, struct file *filp)
+{
+ filp->private_data = node->i_private;
+
+ return 0;
+}
+
+static long imx_secvio_sc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct device *dev = file->private_data;
+ struct secvio_sc_notifier_info info;
+ int ret;
+
+ switch (cmd) {
+ case IMX_SECVIO_SC_GET_STATE:
+ ret = int_imx_secvio_sc_get_state(dev, &info);
+ if (ret) {
+ dev_err(dev, "Fail to get state\n");
+ goto exit;
+ }
+
+ ret = copy_to_user((void *)arg, &info, sizeof(info));
+ if (ret) {
+ dev_err(dev, "Fail to copy info to user\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+ break;
+ case IMX_SECVIO_SC_CHECK_STATE:
+ ret = int_imx_secvio_sc_check_state(dev);
+ if (ret) {
+ dev_err(dev, "Fail to check state\n");
+ goto exit;
+ }
+ break;
+ case IMX_SECVIO_SC_CLEAR_STATE:
+ ret = copy_from_user(&info, (void *)arg, sizeof(info));
+ if (ret) {
+ dev_err(dev, "Fail to copy info from user\n");
+ ret = -EFAULT;
+ goto exit;
+ }
+
+ ret = int_imx_secvio_sc_clear_state(dev, info.hpsvs, info.lps,
+ info.lptds);
+ if (ret) {
+ dev_err(dev, "Fail to clear state\n");
+ goto exit;
+ }
+ break;
+ default:
+ ret = -ENOIOCTLCMD;
+ }
+
+exit:
+ return ret;
+}
+
+static struct file_operations imx_secvio_sc_fops = {
+ .owner = THIS_MODULE,
+ .open = imx_secvio_sc_open,
+ .unlocked_ioctl = imx_secvio_sc_ioctl,
+};
+
+static void if_misc_deregister(void *miscdevice)
+{
+ misc_deregister(miscdevice);
+}
+
+static int imx_secvio_sc_setup(struct device *dev)
+{
+ struct imx_secvio_sc_data *data;
+ u32 seco_version = 0;
+ bool own_secvio;
+ u32 irq_status;
+ int ret = 0;
+
+ if (!devres_open_group(dev, NULL, GFP_KERNEL)) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ /* Allocate private data */
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ dev_err(dev, "Failed to allocate mem for data\n");
+ goto clean;
+ }
+
+ data->dev = dev;
+
+ dev_set_drvdata(dev, data);
+
+ data->nvmem = devm_nvmem_device_get(dev, NULL);
+ if (IS_ERR(data->nvmem)) {
+ dev_err(dev, "Failed to retrieve nvmem\n");
+ return PTR_ERR(data->nvmem);
+ }
+
+ /* Get a handle */
+ ret = imx_scu_get_handle(&data->ipc_handle);
+ if (ret) {
+ dev_err(dev, "cannot get handle to scu: %d\n", ret);
+ goto clean;
+ };
+
+ /* Check the version of the SECO */
+ ret = imx_sc_seco_build_info(data->ipc_handle, &seco_version, NULL);
+ if (ret) {
+ dev_err(dev, "Failed to get seco version\n");
+ goto clean;
+ }
+
+ if ((seco_version & SECO_VERSION_MINOR_MASK) <
+ SECO_MINOR_VERSION_SUPPORT_SECVIO_TAMPER) {
+ dev_err(dev, "SECO version %.8x doesn't support all secvio\n",
+ seco_version);
+ ret = -ENOTSUPP;
+ goto clean;
+ }
+
+ /* Init debug FS */
+ ret = imx_secvio_sc_debugfs(dev);
+ if (ret) {
+ dev_err(dev, "Failed to set debugfs\n");
+ goto clean;
+ }
+
+ /* Check we own the SECVIO */
+ ret = imx_sc_rm_is_resource_owned(data->ipc_handle, IMX_SC_R_SECVIO);
+ if (ret < 0) {
+ dev_err(dev, "Failed to retrieve secvio ownership\n");
+ goto clean;
+ }
+
+ own_secvio = ret > 0;
+ if (!own_secvio) {
+ dev_err(dev, "Secvio resource is not owned\n");
+ ret = -EPERM;
+ goto clean;
+ }
+
+ /* Check IRQ exists and enable it */
+ ret = imx_scu_irq_get_status(IMX_SC_IRQ_GROUP_WAKE, &irq_status);
+ if (ret) {
+ dev_err(dev, "Cannot get IRQ state: %d\n", ret);
+ goto clean;
+ }
+
+ ret = int_imx_secvio_sc_enable_irq(dev);
+ if (ret) {
+ dev_err(dev, "Failed to enable IRQ\n");
+ goto clean;
+ }
+
+ ret = devm_add_action_or_reset(dev, if_imx_secvio_sc_disable_irq, dev);
+ if (ret) {
+ dev_err(dev, "Failed to add managed action to disable IRQ\n");
+ goto clean;
+ }
+
+ /* Register the notifier for IRQ from SNVS */
+ data->irq_nb.notifier_call = imx_secvio_sc_notify;
+ ret = imx_scu_irq_register_notifier(&data->irq_nb);
+ if (ret) {
+ dev_err(dev, "Failed to register IRQ notification handler\n");
+ goto clean;
+ }
+
+ ret = devm_add_action_or_reset(dev, if_imx_scu_irq_register_notifier,
+ &data->irq_nb);
+ if (ret) {
+ dev_err(dev, "Failed to add action to remove irq notif\n");
+ goto clean;
+ }
+
+ /* Register the notification for reporting to user */
+ data->report_nb.notifier_call = report_to_user_notify;
+ ret = register_imx_secvio_sc_notifier(&data->report_nb);
+ if (ret) {
+ dev_err(dev, "Failed to register report notif handler\n");
+ goto clean;
+ }
+
+ ret = devm_add_action_or_reset(dev, if_unregister_imx_secvio_sc_notifier,
+ &data->report_nb);
+ if (ret) {
+ dev_err(dev, "Failed to add action to remove report notif\n");
+ goto clean;
+ }
+
+ /* Register misc device for IOCTL */
+ data->miscdev.name = devm_kstrdup(dev, "secvio-sc", GFP_KERNEL);
+ data->miscdev.minor = MISC_DYNAMIC_MINOR;
+ data->miscdev.fops = &imx_secvio_sc_fops;
+ data->miscdev.parent = dev;
+ ret = misc_register(&data->miscdev);
+ if (ret) {
+ dev_err(dev, "failed to register misc device\n");
+ goto exit;
+ }
+
+ ret = devm_add_action_or_reset(dev, if_misc_deregister, &data->miscdev);
+ if (ret) {
+ dev_err(dev, "Failed to add action to unregister miscdev\n");
+ goto clean;
+ }
+
+ gs_imx_secvio_sc_dev = dev;
+
+ /* Process current state of the secvio and tampers */
+ int_imx_secvio_sc_check_state(dev);
+
+ devres_remove_group(dev, NULL);
+
+ goto exit;
+
+clean:
+ devres_release_group(dev, NULL);
+
+exit:
+ return ret;
+}
+
+static int imx_secvio_sc_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+
+ ret = imx_secvio_sc_setup(dev);
+ if (ret)
+ dev_err(dev, "Failed to setup\n");
+
+ return ret;
+}
+
+static const struct of_device_id imx_secvio_sc_dt_ids[] = {
+ { .compatible = "fsl,imx-sc-secvio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_secvio_sc_dt_ids);
+
+static struct platform_driver imx_secvio_sc_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "imx-secvio-sc",
+ .of_match_table = imx_secvio_sc_dt_ids,
+ },
+ .probe = imx_secvio_sc_probe,
+};
+module_platform_driver(imx_secvio_sc_driver);
+
+MODULE_AUTHOR("Franck LENORMAND <franck.lenormand@nxp.com>");
+MODULE_DESCRIPTION("NXP i.MX driver to handle SNVS secvio irq sent by SCFW");
+MODULE_LICENSE("GPL");