summaryrefslogtreecommitdiffstats
path: root/drivers/crypto/caam/jr.c
diff options
context:
space:
mode:
authorSteffen Trumtrar <s.trumtrar@pengutronix.de>2016-02-12 14:12:38 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2016-02-12 15:55:55 +0100
commit94844727a86e6445c878ebfa0ade907610248ced (patch)
tree2928f2c4afa0d4e2ed6b5311f5eab4d182316a67 /drivers/crypto/caam/jr.c
parent1bfe0f66d705e188a102661399506b20cc7571f4 (diff)
downloadbarebox-94844727a86e6445c878ebfa0ade907610248ced.tar.gz
barebox-94844727a86e6445c878ebfa0ade907610248ced.tar.xz
crypto: add i.MX6 CAAM support
Add the i.MX6 crypto core CAAM with support for the random number generator. The core itself works with jobrings in which descriptors can be queued/dequeued for processing. Depending on descriptor type the CAAM unit then either produces random numbers or decrypts/encrypts data. The code is based on the Linux v4.1 driver of the same name without all the crypto/hashing components. Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/crypto/caam/jr.c')
-rw-r--r--drivers/crypto/caam/jr.c348
1 files changed, 348 insertions, 0 deletions
diff --git a/drivers/crypto/caam/jr.c b/drivers/crypto/caam/jr.c
new file mode 100644
index 0000000000..8f169d4060
--- /dev/null
+++ b/drivers/crypto/caam/jr.c
@@ -0,0 +1,348 @@
+/*
+* CAAM/SEC 4.x transport/backend driver
+* JobR backend functionality
+ *
+ * Copyright 2008-2015 Freescale Semiconductor, Inc.
+ */
+
+#include <common.h>
+#include <clock.h>
+#include <dma.h>
+#include <driver.h>
+#include <init.h>
+#include <linux/barebox-wrapper.h>
+#include <linux/spinlock.h>
+#include <linux/circ_buf.h>
+#include <linux/compiler.h>
+#include <linux/err.h>
+
+#include "regs.h"
+#include "jr.h"
+#include "desc.h"
+#include "intern.h"
+
+/*
+ * The DMA address registers in the JR are a pair of 32-bit registers.
+ * The layout is:
+ *
+ * base + 0x0000 : most-significant 32 bits
+ * base + 0x0004 : least-significant 32 bits
+ *
+ * The 32-bit version of this core therefore has to write to base + 0x0004
+ * to set the 32-bit wide DMA address. This seems to be independent of the
+ * endianness of the written/read data.
+ */
+
+#define REG64_MS32(reg) ((u32 __iomem *)(reg))
+#define REG64_LS32(reg) ((u32 __iomem *)(reg) + 1)
+
+static inline void wr_reg64(u64 __iomem *reg, u64 data)
+{
+ writel(data >> 32, REG64_MS32(reg));
+ writel(data, REG64_LS32(reg));
+}
+
+static inline u64 rd_reg64(u64 __iomem *reg)
+{
+ return ((u64)readl(REG64_MS32(reg)) << 32 |
+ (u64)readl(REG64_LS32(reg)));
+}
+
+static int caam_reset_hw_jr(struct device_d *dev)
+{
+ struct caam_drv_private_jr *jrp = dev->priv;
+ uint64_t start;
+
+ /* initiate flush (required prior to reset) */
+ writel(JRCR_RESET, &jrp->rregs->jrcommand);
+
+ start = get_time_ns();
+ while ((readl(&jrp->rregs->jrintstatus) & JRINT_ERR_HALT_MASK) ==
+ JRINT_ERR_HALT_INPROGRESS) {
+ if (is_timeout(start, 100 * MSECOND)) {
+ dev_err(dev, "job ring %d timed out on flush\n",
+ jrp->ridx);
+ return -ETIMEDOUT;
+ }
+ }
+
+ /* initiate reset */
+ writel(JRCR_RESET, &jrp->rregs->jrcommand);
+
+ start = get_time_ns();
+ while (readl(&jrp->rregs->jrcommand) & JRCR_RESET) {
+ if (is_timeout(start, 100 * MSECOND)) {
+ dev_err(dev, "job ring %d timed out on reset\n",
+ jrp->ridx);
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+/* Deferred service handler, run as interrupt-fired tasklet */
+static int caam_jr_dequeue(struct caam_drv_private_jr *jrp)
+{
+ int hw_idx, sw_idx, i, head, tail;
+ void (*usercall)(struct device_d *dev, u32 *desc, u32 status, void *arg);
+ u32 *userdesc, userstatus;
+ void *userarg;
+ int found;
+
+ while (readl(&jrp->rregs->outring_used)) {
+ head = jrp->head;
+
+ sw_idx = tail = jrp->tail;
+ hw_idx = jrp->out_ring_read_index;
+
+ found = 0;
+
+ for (i = 0; CIRC_CNT(head, tail + i, JOBR_DEPTH) >= 1; i++) {
+ sw_idx = (tail + i) & (JOBR_DEPTH - 1);
+
+ if (jrp->outring[hw_idx].desc ==
+ jrp->entinfo[sw_idx].desc_addr_dma) {
+ found = 1;
+ break; /* found */
+ }
+ }
+
+ if (!found)
+ return -ENOENT;
+
+ barrier();
+
+ /* mark completed, avoid matching on a recycled desc addr */
+ jrp->entinfo[sw_idx].desc_addr_dma = 0;
+
+ /* Stash callback params for use outside of lock */
+ usercall = jrp->entinfo[sw_idx].callbk;
+ userarg = jrp->entinfo[sw_idx].cbkarg;
+ userdesc = jrp->entinfo[sw_idx].desc_addr_virt;
+ userstatus = jrp->outring[hw_idx].jrstatus;
+
+ barrier();
+
+ /* set done */
+ writel(1, &jrp->rregs->outring_rmvd);
+
+ jrp->out_ring_read_index = (jrp->out_ring_read_index + 1) &
+ (JOBR_DEPTH - 1);
+
+ /*
+ * if this job completed out-of-order, do not increment
+ * the tail. Otherwise, increment tail by 1 plus the
+ * number of subsequent jobs already completed out-of-order
+ */
+ if (sw_idx == tail) {
+ do {
+ tail = (tail + 1) & (JOBR_DEPTH - 1);
+ } while (CIRC_CNT(head, tail, JOBR_DEPTH) >= 1 &&
+ jrp->entinfo[tail].desc_addr_dma == 0);
+
+ jrp->tail = tail;
+ }
+
+ /* Finally, execute user's callback */
+ usercall(jrp->dev, userdesc, userstatus, userarg);
+ }
+
+ return 0;
+}
+
+/* Main per-ring interrupt handler */
+static int caam_jr_interrupt(struct caam_drv_private_jr *jrp)
+{
+ uint64_t start;
+ u32 irqstate;
+
+ start = get_time_ns();
+ while (!(irqstate = readl(&jrp->rregs->jrintstatus))) {
+ if (is_timeout(start, 100 * MSECOND)) {
+ dev_err(jrp->dev, "timeout waiting for interrupt\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ /*
+ * If JobR error, we got more development work to do
+ * Flag a bug now, but we really need to shut down and
+ * restart the queue (and fix code).
+ */
+ if (irqstate & JRINT_JR_ERROR) {
+ dev_err(jrp->dev, "job ring error: irqstate: %08x\n", irqstate);
+ BUG();
+ }
+
+ /* Have valid interrupt at this point, just ACK and trigger */
+ writel(irqstate, &jrp->rregs->jrintstatus);
+
+ return caam_jr_dequeue(jrp);
+}
+
+
+/**
+ * caam_jr_enqueue() - Enqueue a job descriptor head. Returns 0 if OK,
+ * -EBUSY if the queue is full, -EIO if it cannot map the caller's
+ * descriptor.
+ * @dev: device of the job ring to be used.
+ * @desc: points to a job descriptor that execute our request. All
+ * descriptors (and all referenced data) must be in a DMAable
+ * region, and all data references must be physical addresses
+ * accessible to CAAM (i.e. within a PAMU window granted
+ * to it).
+ * @cbk: pointer to a callback function to be invoked upon completion
+ * of this request. This has the form:
+ * callback(struct device *dev, u32 *desc, u32 stat, void *arg)
+ * where:
+ * @dev: contains the job ring device that processed this
+ * response.
+ * @desc: descriptor that initiated the request, same as
+ * "desc" being argued to caam_jr_enqueue().
+ * @status: untranslated status received from CAAM. See the
+ * reference manual for a detailed description of
+ * error meaning, or see the JRSTA definitions in the
+ * register header file
+ * @areq: optional pointer to an argument passed with the
+ * original request
+ * @areq: optional pointer to a user argument for use at callback
+ * time.
+ **/
+int caam_jr_enqueue(struct device_d *dev, u32 *desc,
+ void (*cbk)(struct device_d *dev, u32 *desc,
+ u32 status, void *areq),
+ void *areq)
+{
+ struct caam_drv_private_jr *jrp;
+ struct caam_jrentry_info *head_entry;
+ int head, tail, desc_size;
+
+ desc_size = (*desc & HDR_JD_LENGTH_MASK) * sizeof(u32);
+
+ if (!dev->priv)
+ return -ENODEV;
+
+ jrp = dev->priv;
+
+ head = jrp->head;
+ tail = jrp->tail;
+ if (!readl(&jrp->rregs->inpring_avail) ||
+ CIRC_SPACE(head, tail, JOBR_DEPTH) <= 0) {
+ return -EBUSY;
+ }
+
+ head_entry = &jrp->entinfo[head];
+ head_entry->desc_addr_virt = phys_to_virt((u32) desc);
+ head_entry->desc_size = desc_size;
+ head_entry->callbk = (void *)cbk;
+ head_entry->cbkarg = areq;
+ head_entry->desc_addr_dma = (dma_addr_t)desc;
+
+ if (!jrp->inpring)
+ return -EIO;
+
+ jrp->inpring[jrp->inp_ring_write_index] = (dma_addr_t)desc;
+
+ barrier();
+
+ jrp->inp_ring_write_index = (jrp->inp_ring_write_index + 1) &
+ (JOBR_DEPTH - 1);
+ jrp->head = (head + 1) & (JOBR_DEPTH - 1);
+
+ barrier();
+ writel(1, &jrp->rregs->inpring_jobadd);
+
+ clrbits32(&jrp->rregs->rconfig_lo, JRCFG_IMSK);
+
+ return caam_jr_interrupt(jrp);
+}
+EXPORT_SYMBOL(caam_jr_enqueue);
+
+/*
+ * Init JobR independent of platform property detection
+ */
+static int caam_jr_init(struct device_d *dev)
+{
+ struct caam_drv_private_jr *jrp;
+ dma_addr_t dma_inpring;
+ dma_addr_t dma_outring;
+ int i, error;
+
+ jrp = dev->priv;
+
+ error = caam_reset_hw_jr(dev);
+ if (error)
+ return error;
+
+ jrp->inpring = dma_alloc_coherent(sizeof(*jrp->inpring) * JOBR_DEPTH,
+ &dma_inpring);
+ if (!jrp->inpring)
+ return -ENOMEM;
+
+ jrp->outring = dma_alloc_coherent(sizeof(*jrp->outring) *
+ JOBR_DEPTH, &dma_outring);
+ if (!jrp->outring) {
+ dma_free_coherent(jrp->inpring, 0, sizeof(dma_addr_t) * JOBR_DEPTH);
+ dev_err(dev, "can't allocate job rings for %d\n", jrp->ridx);
+ return -ENOMEM;
+ }
+
+ jrp->entinfo = xzalloc(sizeof(*jrp->entinfo) * JOBR_DEPTH);
+
+ for (i = 0; i < JOBR_DEPTH; i++)
+ jrp->entinfo[i].desc_addr_dma = !0;
+
+ /* Setup rings */
+ jrp->inp_ring_write_index = 0;
+ jrp->out_ring_read_index = 0;
+ jrp->head = 0;
+ jrp->tail = 0;
+
+ wr_reg64(&jrp->rregs->inpring_base, dma_inpring);
+ wr_reg64(&jrp->rregs->outring_base, dma_outring);
+ writel(JOBR_DEPTH, &jrp->rregs->inpring_size);
+ writel(JOBR_DEPTH, &jrp->rregs->outring_size);
+
+ jrp->ringsize = JOBR_DEPTH;
+
+ return 0;
+}
+
+/*
+ * Probe routine for each detected JobR subsystem.
+ */
+int caam_jr_probe(struct device_d *dev)
+{
+ struct device_node *nprop;
+ struct caam_job_ring __iomem *ctrl;
+ struct caam_drv_private_jr *jrpriv;
+ static int total_jobrs;
+ int error;
+
+ jrpriv = xzalloc(sizeof(*jrpriv));
+
+ dev->priv = jrpriv;
+ jrpriv->dev = dev;
+
+ /* save ring identity relative to detection */
+ jrpriv->ridx = total_jobrs++;
+
+ nprop = dev->device_node;
+ /* Get configuration properties from device tree */
+ /* First, get register page */
+ ctrl = dev_get_mem_region(dev, 0);
+ if (IS_ERR(ctrl))
+ return PTR_ERR(ctrl);
+
+ jrpriv->rregs = (struct caam_job_ring __force *)ctrl;
+
+ /* Now do the platform independent part */
+ error = caam_jr_init(dev); /* now turn on hardware */
+ if (error)
+ return error;
+
+ jrpriv->tfm_count = 0;
+
+ return 0;
+}