summaryrefslogtreecommitdiff
path: root/drivers/crypto/dcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/crypto/dcp.c')
-rw-r--r--drivers/crypto/dcp.c253
1 files changed, 240 insertions, 13 deletions
diff --git a/drivers/crypto/dcp.c b/drivers/crypto/dcp.c
index a72d73382778..eb7a83d276b7 100644
--- a/drivers/crypto/dcp.c
+++ b/drivers/crypto/dcp.c
@@ -17,10 +17,16 @@
#include <linux/module.h>
#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/sysdev.h>
+#include <linux/bitops.h>
#include <linux/crypto.h>
#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/err.h>
+#include <linux/sysfs.h>
+#include <linux/fs.h>
#include <crypto/algapi.h>
#include <crypto/aes.h>
#include <crypto/sha.h>
@@ -29,13 +35,21 @@
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
-
+#include <linux/uaccess.h>
+#include <linux/clk.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <asm/cacheflush.h>
#include <mach/hardware.h>
#include "dcp.h"
+#include "dcp_bootstream_ioctl.h"
+
+/* Following data only used by DCP bootstream interface */
+struct dcpboot_dma_area {
+ struct dcp_hw_packet hw_packet;
+ uint16_t block[16];
+};
struct dcp {
struct device *dev;
@@ -46,6 +60,8 @@ struct dcp {
int dcp_vmi_irq;
int dcp_irq;
u32 dcp_regs_base;
+ ulong clock_state;
+ bool chan_in_use[DCP_NUM_CHANNELS];
/* Following buffers used in hashing to meet 64-byte len alignment */
char *buf1;
@@ -55,6 +71,10 @@ struct dcp {
struct dcp_hash_coherent_block *buf1_desc;
struct dcp_hash_coherent_block *buf2_desc;
struct dcp_hash_coherent_block *user_buf_desc;
+
+ /* Following data only used by DCP bootstream interface */
+ struct dcpboot_dma_area *dcpboot_dma_area;
+ dma_addr_t dcpboot_dma_area_phys;
};
/* cipher flags */
@@ -77,6 +97,10 @@ struct dcp {
#define DCP_FILL 0x5000
#define DCP_MODE_MASK 0xf000
+/* clock defines */
+#define CLOCK_ON 1
+#define CLOCK_OFF 0
+
struct dcp_op {
unsigned int flags;
@@ -159,6 +183,45 @@ struct dcp_hash_op {
/* only one */
static struct dcp *global_sdcp;
+static void dcp_clock(struct dcp *sdcp, ulong state, bool force)
+{
+ u32 chan;
+ struct clk *clk = clk_get(sdcp->dev, "dcp_clk");
+
+ /* unless force is true (used during suspend/resume), if any
+ * channel is running, then clk is already on, and must stay on */
+ if (!force)
+ for (chan = 0; chan < DCP_NUM_CHANNELS; chan++)
+ if (sdcp->chan_in_use[chan])
+ goto exit;
+
+ if (state == CLOCK_OFF) {
+ /* gate at clock source */
+ if (!IS_ERR(clk))
+ clk_disable(clk);
+ /* gate at DCP */
+ else
+ __raw_writel(BM_DCP_CTRL_CLKGATE,
+ sdcp->dcp_regs_base + HW_DCP_CTRL_SET);
+
+ sdcp->clock_state = CLOCK_OFF;
+
+ } else {
+ /* ungate at clock source */
+ if (!IS_ERR(clk))
+ clk_enable(clk);
+ /* ungate at DCP */
+ else
+ __raw_writel(BM_DCP_CTRL_CLKGATE,
+ sdcp->dcp_regs_base + HW_DCP_CTRL_CLR);
+
+ sdcp->clock_state = CLOCK_ON;
+ }
+
+exit:
+ return;
+}
+
static void dcp_perform_op(struct dcp_op *op)
{
struct dcp *sdcp = global_sdcp;
@@ -244,6 +307,8 @@ static void dcp_perform_op(struct dcp_op *op)
/* submit the work */
mutex_lock(mutex);
+ dcp_clock(sdcp, CLOCK_ON, false);
+ sdcp->chan_in_use[chan] = true;
__raw_writel(-1, sdcp->dcp_regs_base + HW_DCP_CHnSTAT_CLR(chan));
@@ -272,8 +337,9 @@ static void dcp_perform_op(struct dcp_op *op)
__raw_readl(sdcp->dcp_regs_base +
HW_DCP_CHnSTAT(chan)) & 0xff);
out:
+ sdcp->chan_in_use[chan] = false;
+ dcp_clock(sdcp, CLOCK_OFF, false);
mutex_unlock(mutex);
-
dma_unmap_single(sdcp->dev, pkt_phys, sizeof(*pkt), DMA_TO_DEVICE);
}
@@ -1043,6 +1109,8 @@ static int dcp_sha_init(struct shash_desc *desc)
struct mutex *mutex = &sdcp->op_mutex[HASH_CHAN];
mutex_lock(mutex);
+ dcp_clock(sdcp, CLOCK_ON, false);
+ sdcp->chan_in_use[HASH_CHAN] = true;
op->length = 0;
@@ -1172,6 +1240,8 @@ static int dcp_sha_final(struct shash_desc *desc, u8 *out)
for (i = 0; i < digest_len; i++)
*out++ = *--digest;
+ sdcp->chan_in_use[HASH_CHAN] = false;
+ dcp_clock(sdcp, CLOCK_OFF, false);
mutex_unlock(mutex);
return ret;
@@ -1245,6 +1315,110 @@ static irqreturn_t dcp_irq(int irq, void *context)
return dcp_common_irq(irq, context);
}
+/* DCP bootstream verification interface: uses OTP key for crypto */
+static int dcp_bootstream_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct dcp *sdcp = global_sdcp;
+ struct dcpboot_dma_area *da = sdcp->dcpboot_dma_area;
+ void __user *argp = (void __user *)arg;
+ int chan = ROM_DCP_CHAN;
+ unsigned long timeout;
+ struct mutex *mutex;
+ int retVal;
+
+ /* be paranoid */
+ if (sdcp == NULL)
+ return -EBADF;
+
+ if (cmd != DBS_ENC && cmd != DBS_DEC)
+ return -EINVAL;
+
+ /* copy to (aligned) block */
+ if (copy_from_user(da->block, argp, 16))
+ return -EFAULT;
+
+ mutex = &sdcp->op_mutex[chan];
+ mutex_lock(mutex);
+ dcp_clock(sdcp, CLOCK_ON, false);
+ sdcp->chan_in_use[chan] = true;
+
+ __raw_writel(-1, sdcp->dcp_regs_base +
+ HW_DCP_CHnSTAT_CLR(ROM_DCP_CHAN));
+ __raw_writel(BF(ROM_DCP_CHAN_MASK, DCP_STAT_IRQ),
+ sdcp->dcp_regs_base + HW_DCP_STAT_CLR);
+
+ da->hw_packet.pNext = 0;
+ da->hw_packet.pkt1 = BM_DCP_PACKET1_DECR_SEMAPHORE |
+ BM_DCP_PACKET1_ENABLE_CIPHER | BM_DCP_PACKET1_OTP_KEY |
+ BM_DCP_PACKET1_INTERRUPT |
+ (cmd == DBS_ENC ? BM_DCP_PACKET1_CIPHER_ENCRYPT : 0);
+ da->hw_packet.pkt2 = BF(0, DCP_PACKET2_CIPHER_CFG) |
+ BF(0, DCP_PACKET2_KEY_SELECT) |
+ BF(BV_DCP_PACKET2_CIPHER_MODE__ECB, DCP_PACKET2_CIPHER_MODE) |
+ BF(BV_DCP_PACKET2_CIPHER_SELECT__AES128, DCP_PACKET2_CIPHER_SELECT);
+ da->hw_packet.pSrc = sdcp->dcpboot_dma_area_phys +
+ offsetof(struct dcpboot_dma_area, block);
+ da->hw_packet.pDst = da->hw_packet.pSrc; /* in-place */
+ da->hw_packet.size = 16;
+ da->hw_packet.pPayload = 0;
+ da->hw_packet.stat = 0;
+
+ /* Load the work packet pointer and bump the channel semaphore */
+ __raw_writel(sdcp->dcpboot_dma_area_phys +
+ offsetof(struct dcpboot_dma_area, hw_packet),
+ sdcp->dcp_regs_base + HW_DCP_CHnCMDPTR(ROM_DCP_CHAN));
+
+ sdcp->wait[chan] = 0;
+ __raw_writel(BF(1, DCP_CHnSEMA_INCREMENT),
+ sdcp->dcp_regs_base + HW_DCP_CHnSEMA(ROM_DCP_CHAN));
+
+ timeout = jiffies + msecs_to_jiffies(100);
+
+ while (time_before(jiffies, timeout) && sdcp->wait[chan] == 0)
+ cpu_relax();
+
+ if (!time_before(jiffies, timeout)) {
+ dev_err(sdcp->dev,
+ "Timeout while waiting for operation to complete\n");
+ retVal = -ETIMEDOUT;
+ goto exit;
+ }
+
+ if ((__raw_readl(sdcp->dcp_regs_base + HW_DCP_CHnSTAT(ROM_DCP_CHAN))
+ & 0xff) != 0) {
+ dev_err(sdcp->dev, "Channel stat error 0x%02x\n",
+ __raw_readl(sdcp->dcp_regs_base +
+ HW_DCP_CHnSTAT(ROM_DCP_CHAN)) & 0xff);
+ retVal = -EFAULT;
+ goto exit;
+ }
+
+ if (copy_to_user(argp, da->block, 16)) {
+ retVal = -EFAULT;
+ goto exit;
+ }
+
+ retVal = 0;
+
+exit:
+ sdcp->chan_in_use[chan] = false;
+ dcp_clock(sdcp, CLOCK_OFF, false);
+ mutex_unlock(mutex);
+ return retVal;
+}
+
+static const struct file_operations dcp_bootstream_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = dcp_bootstream_ioctl,
+};
+
+static struct miscdevice dcp_bootstream_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "dcpboot",
+ .fops = &dcp_bootstream_fops,
+};
+
static int dcp_probe(struct platform_device *pdev)
{
struct dcp *sdcp = NULL;
@@ -1272,6 +1446,7 @@ static int dcp_probe(struct platform_device *pdev)
for (i = 0; i < DCP_NUM_CHANNELS; i++) {
mutex_init(&sdcp->op_mutex[i]);
init_completion(&sdcp->op_wait[i]);
+ sdcp->chan_in_use[i] = false;
}
platform_set_drvdata(pdev, sdcp);
@@ -1282,7 +1457,8 @@ static int dcp_probe(struct platform_device *pdev)
ret = -ENXIO;
goto err_kfree;
}
- sdcp->dcp_regs_base = (u32) IO_ADDRESS(r->start);
+ sdcp->dcp_regs_base = (u32) ioremap(r->start, r->end - r->start + 1);
+ dcp_clock(sdcp, CLOCK_ON, true);
/* Soft reset and remove the clock gate */
__raw_writel(BM_DCP_CTRL_SFTRST, sdcp->dcp_regs_base + HW_DCP_CTRL_SET);
@@ -1318,14 +1494,14 @@ static int dcp_probe(struct platform_device *pdev)
if (!r) {
dev_err(&pdev->dev, "can't get IRQ resource (0)\n");
ret = -EIO;
- goto err_kfree;
+ goto err_gate_clk;
}
sdcp->dcp_vmi_irq = r->start;
ret = request_irq(sdcp->dcp_vmi_irq, dcp_vmi_irq, 0, "dcp",
sdcp);
if (ret != 0) {
dev_err(&pdev->dev, "can't request_irq (0)\n");
- goto err_kfree;
+ goto err_gate_clk;
}
r = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
@@ -1346,7 +1522,7 @@ static int dcp_probe(struct platform_device *pdev)
ret = crypto_register_alg(&dcp_aes_alg);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register aes crypto\n");
- goto err_kfree;
+ goto err_free_irq1;
}
ret = crypto_register_alg(&dcp_aes_ecb_alg);
@@ -1439,9 +1615,30 @@ static int dcp_probe(struct platform_device *pdev)
}
}
+ /* register dcpboot interface to allow apps (such as kobs-ng) to
+ * verify files (such as the bootstream) using the OTP key for crypto */
+ ret = misc_register(&dcp_bootstream_misc);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Unable to register misc device\n");
+ goto err_unregister_sha1;
+ }
+
+ sdcp->dcpboot_dma_area = dma_alloc_coherent(&pdev->dev,
+ sizeof(*sdcp->dcpboot_dma_area), &sdcp->dcpboot_dma_area_phys,
+ GFP_KERNEL);
+ if (sdcp->dcpboot_dma_area == NULL) {
+ dev_err(&pdev->dev,
+ "Unable to allocate DMAable memory \
+ for dcpboot interface\n");
+ goto err_dereg;
+ }
+
+ dcp_clock(sdcp, CLOCK_OFF, false);
dev_notice(&pdev->dev, "DCP crypto enabled.!\n");
return 0;
+err_dereg:
+ misc_deregister(&dcp_bootstream_misc);
err_unregister_sha1:
crypto_unregister_shash(&dcp_sha1_alg);
err_unregister_aes_cbc:
@@ -1450,8 +1647,12 @@ err_unregister_aes_ecb:
crypto_unregister_alg(&dcp_aes_ecb_alg);
err_unregister_aes:
crypto_unregister_alg(&dcp_aes_alg);
+err_free_irq1:
+ free_irq(sdcp->dcp_irq, sdcp);
err_free_irq0:
free_irq(sdcp->dcp_vmi_irq, sdcp);
+err_gate_clk:
+ dcp_clock(sdcp, CLOCK_OFF, false);
err_kfree:
kfree(sdcp);
err:
@@ -1466,6 +1667,8 @@ static int dcp_remove(struct platform_device *pdev)
sdcp = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
+ dcp_clock(sdcp, CLOCK_ON, false);
+
free_irq(sdcp->dcp_irq, sdcp);
free_irq(sdcp->dcp_vmi_irq, sdcp);
@@ -1487,34 +1690,58 @@ static int dcp_remove(struct platform_device *pdev)
sdcp->user_buf_desc, sdcp->user_buf_desc->my_phys);
}
+ if (sdcp->dcpboot_dma_area) {
+ dma_free_coherent(&pdev->dev, sizeof(*sdcp->dcpboot_dma_area),
+ sdcp->dcpboot_dma_area, sdcp->dcpboot_dma_area_phys);
+ misc_deregister(&dcp_bootstream_misc);
+ }
+
+
crypto_unregister_shash(&dcp_sha1_alg);
- crypto_unregister_shash(&dcp_sha256_alg);
+
+ if (__raw_readl(sdcp->dcp_regs_base + HW_DCP_CAPABILITY1) &
+ BF_DCP_CAPABILITY1_HASH_ALGORITHMS(
+ BV_DCP_CAPABILITY1_HASH_ALGORITHMS__SHA256))
+ crypto_unregister_shash(&dcp_sha256_alg);
crypto_unregister_alg(&dcp_aes_cbc_alg);
crypto_unregister_alg(&dcp_aes_ecb_alg);
crypto_unregister_alg(&dcp_aes_alg);
+
+ dcp_clock(sdcp, CLOCK_OFF, true);
+ iounmap((void *) sdcp->dcp_regs_base);
kfree(sdcp);
global_sdcp = NULL;
return 0;
}
-
-#ifdef CONFIG_PM
static int dcp_suspend(struct platform_device *pdev,
pm_message_t state)
{
+#ifdef CONFIG_PM
+ struct dcp *sdcp = platform_get_drvdata(pdev);
+
+ if (sdcp->clock_state == CLOCK_ON) {
+ dcp_clock(sdcp, CLOCK_OFF, true);
+ /* indicate that clock needs to be turned on upon resume */
+ sdcp->clock_state = CLOCK_ON;
+ }
+#endif
return 0;
}
static int dcp_resume(struct platform_device *pdev)
{
+#ifdef CONFIG_PM
+ struct dcp *sdcp = platform_get_drvdata(pdev);
+
+ /* if clock was on prior to suspend, turn it back on */
+ if (sdcp->clock_state == CLOCK_ON)
+ dcp_clock(sdcp, CLOCK_ON, true);
+#endif
return 0;
}
-#else
-#define dcp_suspend NULL
-#define dcp_resume NULL
-#endif
static struct platform_driver dcp_driver = {
.probe = dcp_probe,