diff options
Diffstat (limited to 'drivers/crypto/caam/secvio.c')
-rw-r--r-- | drivers/crypto/caam/secvio.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/drivers/crypto/caam/secvio.c b/drivers/crypto/caam/secvio.c new file mode 100644 index 000000000000..b0719e4173b4 --- /dev/null +++ b/drivers/crypto/caam/secvio.c @@ -0,0 +1,310 @@ + +/* + * CAAM/SEC 4.x Security Violation Handler + * Copyright 2012 Freescale Semiconductor, Inc., All Rights Reserved + */ + +#include "compat.h" +#include "intern.h" +#include "secvio.h" +#include "regs.h" + +/* + * 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 Security Violation", + "JTAG Alarm", + "Watchdog", + "(reserved)", + "External Boot", + "Tamper Detect", +}; + +/* Top-level security violation interrupt */ +static irqreturn_t caam_secvio_interrupt(int irq, void *snvsdev) +{ + struct device *dev = snvsdev; + struct caam_drv_private_secvio *svpriv = dev_get_drvdata(dev); + u32 irqstate; + + /* Check the HP secvio status register */ + irqstate = rd_reg32(&svpriv->svregs->hp.secvio_status) | + HP_SECVIOST_SECVIOMASK; + if (!irqstate) + return IRQ_NONE; + + /* Mask out one or more causes for deferred service */ + clrbits32(&svpriv->svregs->hp.secvio_int_ctl, irqstate); + + /* Now ACK causes */ + setbits32(&svpriv->svregs->hp.secvio_status, irqstate); + + /* And run deferred service */ + preempt_disable(); + tasklet_schedule(&svpriv->irqtask[smp_processor_id()]); + preempt_enable(); + + return IRQ_HANDLED; +} + +/* Deferred service handler. Tasklet arg is simply the SNVS dev */ +static void caam_secvio_dispatch(unsigned long indev) +{ + struct device *dev = (struct device *)indev; + struct caam_drv_private_secvio *svpriv = dev_get_drvdata(dev); + unsigned long flags, cause; + int i; + + + /* + * Capture the interrupt cause, using masked interrupts as + * identification. This only works if all are enabled; if + * this changes in the future, a "cause queue" will have to + * be built + */ + cause = rd_reg32(&svpriv->svregs->hp.secvio_int_ctl) & + (HP_SECVIO_INTEN_SRC5 | HP_SECVIO_INTEN_SRC4 | + HP_SECVIO_INTEN_SRC3 | HP_SECVIO_INTEN_SRC2 | + HP_SECVIO_INTEN_SRC1 | HP_SECVIO_INTEN_SRC0); + + /* Look through causes, call each handler if exists */ + for (i = 0; i < MAX_SECVIO_SOURCES; i++) + if (cause & (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 */ + setbits32(&svpriv->svregs->hp.secvio_int_ctl, cause); +} + +/* + * 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 caam_secvio_default(struct device *dev, u32 cause, void *ext) +{ + struct caam_drv_private_secvio *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 caam_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 caam_drv_private_secvio *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(caam_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 caam_secvio_remove_handler(struct device *dev, enum secvio_cause cause) +{ + unsigned long flags; + struct caam_drv_private_secvio *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 = caam_secvio_default; + svpriv->intsrc[cause].ext = NULL; + spin_unlock_irqrestore(&svpriv->svlock, flags); + return 0; +} +EXPORT_SYMBOL(caam_secvio_remove_handler); + +int caam_secvio_startup(struct platform_device *pdev) +{ + struct device *ctrldev, *svdev; + struct caam_drv_private *ctrlpriv; + struct caam_drv_private_secvio *svpriv; + struct platform_device *svpdev; + int i, error; + + ctrldev = &pdev->dev; + ctrlpriv = dev_get_drvdata(ctrldev); + + /* + * Set up the private block for secure memory + * Only one instance is possible + */ + svpriv = kzalloc(sizeof(struct caam_drv_private_secvio), GFP_KERNEL); + if (svpriv == NULL) { + dev_err(ctrldev, "can't alloc private mem for secvio\n"); + return -ENOMEM; + } + svpriv->parentdev = ctrldev; + + /* Create the security violation dev */ +#ifdef CONFIG_OF + svpdev = of_platform_device_create(np, NULL, ctrldev); +#else + svpdev = platform_device_register_data(ctrldev, "caam_secvio", 0, + svpriv, + sizeof(struct caam_drv_private_secvio)); +#endif + if (svpdev == NULL) { + kfree(svpriv); + return -EINVAL; + } + svdev = &svpdev->dev; + dev_set_drvdata(svdev, svpriv); + ctrlpriv->secviodev = svdev; + svpriv->svregs = ctrlpriv->snvs; + + /* + * Now we have all the dev data set up. Init interrupt + * source descriptions + */ + for (i = 0; i < MAX_SECVIO_SOURCES; i++) { + svpriv->intsrc[i].intname = violation_src_name[i]; + svpriv->intsrc[i].handler = caam_secvio_default; + } + + /* Connect main handler */ + for_each_possible_cpu(i) + tasklet_init(&svpriv->irqtask[i], caam_secvio_dispatch, + (unsigned long)svdev); + + error = request_irq(ctrlpriv->secvio_irq, caam_secvio_interrupt, + IRQF_SHARED, "caam-secvio", svdev); + if (error) { + dev_err(svdev, "can't connect secvio interrupt\n"); + irq_dispose_mapping(ctrlpriv->secvio_irq); + ctrlpriv->secvio_irq = 0; + return -EINVAL; + } + + /* Enable all sources */ + wr_reg32(&svpriv->svregs->hp.secvio_int_ctl, HP_SECVIO_INTEN_ALL); + + dev_info(svdev, "security violation service handlers armed\n"); + + return 0; +} + +void caam_secvio_shutdown(struct platform_device *pdev) +{ + struct device *ctrldev, *svdev; + struct caam_drv_private *priv; + struct caam_drv_private_secvio *svpriv; + int i; + + ctrldev = &pdev->dev; + priv = dev_get_drvdata(ctrldev); + svdev = priv->secviodev; + svpriv = dev_get_drvdata(svdev); + + /* Shut off all sources */ + wr_reg32(&svpriv->svregs->hp.secvio_int_ctl, 0); + + /* Remove tasklets and release interrupt */ + for_each_possible_cpu(i) + tasklet_kill(&svpriv->irqtask[i]); + + free_irq(priv->secvio_irq, svdev); + + kfree(svpriv); +} + + +#ifdef CONFIG_OF +static void __exit caam_secvio_exit(void) +{ + struct device_node *dev_node; + struct platform_device *pdev; + + dev_node = of_find_compatible_node(NULL, NULL, "fsl,sec-v4.0"); + if (!dev_node) { + dev_node = of_find_compatible_node(NULL, NULL, "fsl,sec4.0"); + if (!dev_node) + return -ENODEV; + } + + pdev = of_find_device_by_node(dev_node); + if (!pdev) + return -ENODEV; + + of_node_put(dev_node); + + caam_sm_shutdown(pdev); +} + +static int __init caam_secvio_init(void) +{ + struct device_node *dev_node; + struct platform_device *pdev; + + /* + * Do of_find_compatible_node() then of_find_device_by_node() + * once a functional device tree is available + */ + dev_node = of_find_compatible_node(NULL, NULL, "fsl,sec-v4.0"); + if (!dev_node) { + dev_node = of_find_compatible_node(NULL, NULL, "fsl,sec4.0"); + if (!dev_node) + return -ENODEV; + } + + pdev = of_find_device_by_node(dev_node); + if (!pdev) + return -ENODEV; + + of_node_put(dev_node); + + return caam_secvio_startup(pdev); +} + +module_init(caam_secvio_init); +module_exit(caam_secvio_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("FSL CAAM/SNVS Security Violation Handler"); +MODULE_AUTHOR("Freescale Semiconductor - NMSG/MAD"); +#endif |