summaryrefslogtreecommitdiff
path: root/drivers/crypto/caam/secvio.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/crypto/caam/secvio.c')
-rw-r--r--drivers/crypto/caam/secvio.c340
1 files changed, 340 insertions, 0 deletions
diff --git a/drivers/crypto/caam/secvio.c b/drivers/crypto/caam/secvio.c
new file mode 100644
index 000000000000..c6a29d49de68
--- /dev/null
+++ b/drivers/crypto/caam/secvio.c
@@ -0,0 +1,340 @@
+
+/*
+ * SNVS Security Violation Handler
+ * Copyright (C) 2012-2016 Freescale Semiconductor, Inc., All Rights Reserved
+ */
+
+#include "compat.h"
+#include "intern.h"
+#include "secvio.h"
+#include "regs.h"
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+
+/* The driver is matched with node caam_snvs to get regmap
+ * It will then retrieve interruption and tamper alarm configuration from
+ * node caam-secvio searching for the compat string "fsl,imx6q-caam-secvio"
+ */
+#define DRIVER_NAME "caam-snvs"
+
+/*
+ * These names are associated with each violation handler.
+ * The source names were taken from MX6, and are based on recommendations
+ * for most common SoCs.
+ */
+static const u8 *violation_src_name[] = {
+ "CAAM Internal Security Violation",
+ "JTAG Alarm",
+ "Watchdog",
+ "(reserved)",
+ "External Boot",
+ "External Tamper Detect",
+};
+
+/* These names help describe security monitor state for the console */
+static const u8 *snvs_ssm_state_name[] = {
+ "init",
+ "hard fail",
+ "(undef:2)",
+ "soft fail",
+ "(undef:4)",
+ "(undef:5)",
+ "(undef:6)",
+ "(undef:7)",
+ "transition",
+ "check",
+ "(undef:10)",
+ "non-secure",
+ "(undef:12)",
+ "trusted",
+ "(undef:14)",
+ "secure",
+};
+
+/* Top-level security violation interrupt */
+static irqreturn_t snvs_secvio_interrupt(int irq, void *snvsdev)
+{
+ struct device *dev = snvsdev;
+ struct snvs_secvio_drv_private *svpriv = dev_get_drvdata(dev);
+
+ clk_enable(svpriv->clk);
+ /* Check the HP secvio status register */
+ svpriv->irqcause = rd_reg32(&svpriv->svregs->hp.secvio_status) &
+ HP_SECVIOST_SECVIOMASK;
+
+ if (!svpriv->irqcause) {
+ clk_disable(svpriv->clk);
+ return IRQ_NONE;
+ }
+
+ /* Now ACK cause */
+ clrsetbits_32(&svpriv->svregs->hp.secvio_status, 0, svpriv->irqcause);
+
+ /* And run deferred service */
+ preempt_disable();
+ tasklet_schedule(&svpriv->irqtask[smp_processor_id()]);
+ preempt_enable();
+
+ clk_disable(svpriv->clk);
+
+ return IRQ_HANDLED;
+}
+
+/* Deferred service handler. Tasklet arg is simply the SNVS dev */
+static void snvs_secvio_dispatch(unsigned long indev)
+{
+ struct device *dev = (struct device *)indev;
+ struct snvs_secvio_drv_private *svpriv = dev_get_drvdata(dev);
+ unsigned long flags;
+ int i;
+
+
+ /* Look through stored causes, call each handler if exists */
+ for (i = 0; i < MAX_SECVIO_SOURCES; i++)
+ if (svpriv->irqcause & (1 << i)) {
+ spin_lock_irqsave(&svpriv->svlock, flags);
+ svpriv->intsrc[i].handler(dev, i,
+ svpriv->intsrc[i].ext);
+ spin_unlock_irqrestore(&svpriv->svlock, flags);
+ };
+
+ /* Re-enable now-serviced interrupts */
+ clrsetbits_32(&svpriv->svregs->hp.secvio_intcfg, 0, svpriv->irqcause);
+}
+
+/*
+ * Default cause handler, used in lieu of an application-defined handler.
+ * All it does at this time is print a console message. It could force a halt.
+ */
+static void snvs_secvio_default(struct device *dev, u32 cause, void *ext)
+{
+ struct snvs_secvio_drv_private *svpriv = dev_get_drvdata(dev);
+
+ dev_err(dev, "Unhandled Security Violation Interrupt %d = %s\n",
+ cause, svpriv->intsrc[cause].intname);
+}
+
+/*
+ * Install an application-defined handler for a specified cause
+ * Arguments:
+ * - dev points to SNVS-owning device
+ * - cause interrupt source cause
+ * - handler application-defined handler, gets called with dev
+ * source cause, and locally-defined handler argument
+ * - cause_description points to a string to override the default cause
+ * name, this can be used as an alternate for error
+ * messages and such. If left NULL, the default
+ * description string is used.
+ * - ext pointer to any extra data needed by the handler.
+ */
+int snvs_secvio_install_handler(struct device *dev, enum secvio_cause cause,
+ void (*handler)(struct device *dev, u32 cause,
+ void *ext),
+ u8 *cause_description, void *ext)
+{
+ unsigned long flags;
+ struct snvs_secvio_drv_private *svpriv;
+
+ svpriv = dev_get_drvdata(dev);
+
+ if ((handler == NULL) || (cause > SECVIO_CAUSE_SOURCE_5))
+ return -EINVAL;
+
+ spin_lock_irqsave(&svpriv->svlock, flags);
+ svpriv->intsrc[cause].handler = handler;
+ if (cause_description != NULL)
+ svpriv->intsrc[cause].intname = cause_description;
+ if (ext != NULL)
+ svpriv->intsrc[cause].ext = ext;
+ spin_unlock_irqrestore(&svpriv->svlock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(snvs_secvio_install_handler);
+
+/*
+ * Remove an application-defined handler for a specified cause (and, by
+ * implication, restore the "default".
+ * Arguments:
+ * - dev points to SNVS-owning device
+ * - cause interrupt source cause
+ */
+int snvs_secvio_remove_handler(struct device *dev, enum secvio_cause cause)
+{
+ unsigned long flags;
+ struct snvs_secvio_drv_private *svpriv;
+
+ svpriv = dev_get_drvdata(dev);
+
+ if (cause > SECVIO_CAUSE_SOURCE_5)
+ return -EINVAL;
+
+ spin_lock_irqsave(&svpriv->svlock, flags);
+ svpriv->intsrc[cause].intname = violation_src_name[cause];
+ svpriv->intsrc[cause].handler = snvs_secvio_default;
+ svpriv->intsrc[cause].ext = NULL;
+ spin_unlock_irqrestore(&svpriv->svlock, flags);
+ return 0;
+}
+EXPORT_SYMBOL(snvs_secvio_remove_handler);
+
+static int snvs_secvio_remove(struct platform_device *pdev)
+{
+ struct device *svdev;
+ struct snvs_secvio_drv_private *svpriv;
+ int i;
+
+ svdev = &pdev->dev;
+ svpriv = dev_get_drvdata(svdev);
+
+ clk_enable(svpriv->clk);
+ /* Set all sources to nonfatal */
+ wr_reg32(&svpriv->svregs->hp.secvio_intcfg, 0);
+
+ /* Remove tasklets and release interrupt */
+ for_each_possible_cpu(i)
+ tasklet_kill(&svpriv->irqtask[i]);
+
+ clk_disable_unprepare(svpriv->clk);
+ free_irq(svpriv->irq, svdev);
+ iounmap(svpriv->svregs);
+ kfree(svpriv);
+
+ return 0;
+}
+
+static int snvs_secvio_probe(struct platform_device *pdev)
+{
+ struct device *svdev;
+ struct snvs_secvio_drv_private *svpriv;
+ struct device_node *np, *npirq;
+ struct snvs_full __iomem *snvsregs;
+ int i, error;
+ u32 hpstate;
+ const void *jtd, *wtd, *itd, *etd;
+ u32 td_en;
+
+ svpriv = kzalloc(sizeof(struct snvs_secvio_drv_private), GFP_KERNEL);
+ if (!svpriv)
+ return -ENOMEM;
+
+ svdev = &pdev->dev;
+ dev_set_drvdata(svdev, svpriv);
+ svpriv->pdev = pdev;
+ np = pdev->dev.of_node;
+
+ npirq = of_find_compatible_node(NULL, NULL, "fsl,imx6q-caam-secvio");
+ if (!npirq) {
+ dev_err(svdev, "can't find secvio node\n");
+ kfree(svpriv);
+ return -EINVAL;
+ }
+ svpriv->irq = irq_of_parse_and_map(npirq, 0);
+ if (svpriv->irq <= 0) {
+ dev_err(svdev, "can't identify secvio interrupt\n");
+ kfree(svpriv);
+ return -EINVAL;
+ }
+
+ jtd = of_get_property(npirq, "jtag-tamper", NULL);
+ wtd = of_get_property(npirq, "watchdog-tamper", NULL);
+ itd = of_get_property(npirq, "internal-boot-tamper", NULL);
+ etd = of_get_property(npirq, "external-pin-tamper", NULL);
+ if (!jtd | !wtd | !itd | !etd ) {
+ dev_err(svdev, "can't identify all tamper alarm configuration\n");
+ kfree(svpriv);
+ return -EINVAL;
+ }
+
+ /*
+ * Configure all sources according to device tree property.
+ * If the property is enabled then the source is ser as
+ * fatal violations except LP section,
+ * source #5 (typically used as an external tamper detect), and
+ * source #3 (typically unused). Whenever the transition to
+ * secure mode has occurred, these will now be "fatal" violations
+ */
+ td_en = HP_SECVIO_INTEN_SRC0;
+ if (!strcmp(jtd, "enabled"))
+ td_en |= HP_SECVIO_INTEN_SRC1;
+ if (!strcmp(wtd, "enabled"))
+ td_en |= HP_SECVIO_INTEN_SRC2;
+ if (!strcmp(itd, "enabled"))
+ td_en |= HP_SECVIO_INTEN_SRC4;
+ if (!strcmp(etd, "enabled"))
+ td_en |= HP_SECVIO_INTEN_SRC5;
+
+ snvsregs = of_iomap(np, 0);
+ if (!snvsregs) {
+ dev_err(svdev, "register mapping failed\n");
+ return -ENOMEM;
+ }
+ svpriv->svregs = (struct snvs_full __force *)snvsregs;
+
+ svpriv->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(svpriv->clk)) {
+ dev_err(&pdev->dev, "can't get snvs clock\n");
+ svpriv->clk = NULL;
+ }
+
+ /* Write the Secvio Enable Config the SVCR */
+ wr_reg32(&svpriv->svregs->hp.secvio_ctl, td_en);
+ wr_reg32(&svpriv->svregs->hp.secvio_intcfg, td_en);
+
+ /* Device data set up. Now init interrupt source descriptions */
+ for (i = 0; i < MAX_SECVIO_SOURCES; i++) {
+ svpriv->intsrc[i].intname = violation_src_name[i];
+ svpriv->intsrc[i].handler = snvs_secvio_default;
+ }
+ /* Connect main handler */
+ for_each_possible_cpu(i)
+ tasklet_init(&svpriv->irqtask[i], snvs_secvio_dispatch,
+ (unsigned long)svdev);
+
+ error = request_irq(svpriv->irq, snvs_secvio_interrupt,
+ IRQF_SHARED, DRIVER_NAME, svdev);
+ if (error) {
+ dev_err(svdev, "can't connect secvio interrupt\n");
+ irq_dispose_mapping(svpriv->irq);
+ svpriv->irq = 0;
+ iounmap(svpriv->svregs);
+ kfree(svpriv);
+ return -EINVAL;
+ }
+
+ clk_prepare_enable(svpriv->clk);
+
+ hpstate = (rd_reg32(&svpriv->svregs->hp.status) &
+ HP_STATUS_SSM_ST_MASK) >> HP_STATUS_SSM_ST_SHIFT;
+ dev_info(svdev, "violation handlers armed - %s state\n",
+ snvs_ssm_state_name[hpstate]);
+
+ clk_disable(svpriv->clk);
+
+ return 0;
+}
+
+static struct of_device_id snvs_secvio_match[] = {
+ {
+ .compatible = "fsl,imx6q-caam-snvs",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, snvs_secvio_match);
+
+static struct platform_driver snvs_secvio_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = snvs_secvio_match,
+ },
+ .probe = snvs_secvio_probe,
+ .remove = snvs_secvio_remove,
+};
+
+module_platform_driver(snvs_secvio_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("FSL SNVS Security Violation Handler");
+MODULE_AUTHOR("Freescale Semiconductor - MCU");