aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/crypto/dcp.c
blob: a8a7dd4b0d25c6741d47e40b53b9d3cfd7f101ad (plain) (tree)




































































































































































































                                                                              
                                  






























                                                                     
                                                                         





                                                                       
                                           












































































































































































                                                                                                  
                             



                                                           
                                            




















































































































































































































































































































                                                                               



                                                                 







                                                                      
                              
         

                                                               



















                                                                        
                            




                                                                        
                            



























































































                                                                               
 







                                                   













                                                                             




                                                   























                                                         
/*
 * Cryptographic API.
 *
 * Support for DCP cryptographic accelerator.
 *
 * Copyright (c) 2013
 * Author: Tobias Rauter <tobias.rauter@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 * Based on tegra-aes.c, dcp.c (from freescale SDK) and sahara.c
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/crypto.h>
#include <linux/miscdevice.h>

#include <crypto/scatterwalk.h>
#include <crypto/aes.h>


/* IOCTL for DCP OTP Key AES - taken from Freescale's SDK*/
#define DBS_IOCTL_BASE   'd'
#define DBS_ENC	_IOW(DBS_IOCTL_BASE, 0x00, uint8_t[16])
#define DBS_DEC _IOW(DBS_IOCTL_BASE, 0x01, uint8_t[16])

/* DCP channel used for AES */
#define USED_CHANNEL 1
/* Ring Buffers' maximum size */
#define DCP_MAX_PKG 20

/* Control Register */
#define DCP_REG_CTRL 0x000
#define DCP_CTRL_SFRST (1<<31)
#define DCP_CTRL_CLKGATE (1<<30)
#define DCP_CTRL_CRYPTO_PRESENT (1<<29)
#define DCP_CTRL_SHA_PRESENT (1<<28)
#define DCP_CTRL_GATHER_RES_WRITE (1<<23)
#define DCP_CTRL_ENABLE_CONTEXT_CACHE (1<<22)
#define DCP_CTRL_ENABLE_CONTEXT_SWITCH (1<<21)
#define DCP_CTRL_CH_IRQ_E_0 0x01
#define DCP_CTRL_CH_IRQ_E_1 0x02
#define DCP_CTRL_CH_IRQ_E_2 0x04
#define DCP_CTRL_CH_IRQ_E_3 0x08

/* Status register */
#define DCP_REG_STAT 0x010
#define DCP_STAT_OTP_KEY_READY (1<<28)
#define DCP_STAT_CUR_CHANNEL(stat) ((stat>>24)&0x0F)
#define DCP_STAT_READY_CHANNEL(stat) ((stat>>16)&0x0F)
#define DCP_STAT_IRQ(stat) (stat&0x0F)
#define DCP_STAT_CHAN_0 (0x01)
#define DCP_STAT_CHAN_1 (0x02)
#define DCP_STAT_CHAN_2 (0x04)
#define DCP_STAT_CHAN_3 (0x08)

/* Channel Control Register */
#define DCP_REG_CHAN_CTRL 0x020
#define DCP_CHAN_CTRL_CH0_IRQ_MERGED (1<<16)
#define DCP_CHAN_CTRL_HIGH_PRIO_0 (0x0100)
#define DCP_CHAN_CTRL_HIGH_PRIO_1 (0x0200)
#define DCP_CHAN_CTRL_HIGH_PRIO_2 (0x0400)
#define DCP_CHAN_CTRL_HIGH_PRIO_3 (0x0800)
#define DCP_CHAN_CTRL_ENABLE_0 (0x01)
#define DCP_CHAN_CTRL_ENABLE_1 (0x02)
#define DCP_CHAN_CTRL_ENABLE_2 (0x04)
#define DCP_CHAN_CTRL_ENABLE_3 (0x08)

/*
 * Channel Registers:
 * The DCP has 4 channels. Each of this channels
 * has 4 registers (command pointer, semaphore, status and options).
 * The address of register REG of channel CHAN is obtained by
 * dcp_chan_reg(REG, CHAN)
 */
#define DCP_REG_CHAN_PTR	0x00000100
#define DCP_REG_CHAN_SEMA	0x00000110
#define DCP_REG_CHAN_STAT	0x00000120
#define DCP_REG_CHAN_OPT	0x00000130

#define DCP_CHAN_STAT_NEXT_CHAIN_IS_0	0x010000
#define DCP_CHAN_STAT_NO_CHAIN		0x020000
#define DCP_CHAN_STAT_CONTEXT_ERROR	0x030000
#define DCP_CHAN_STAT_PAYLOAD_ERROR	0x040000
#define DCP_CHAN_STAT_INVALID_MODE	0x050000
#define DCP_CHAN_STAT_PAGEFAULT		0x40
#define DCP_CHAN_STAT_DST		0x20
#define DCP_CHAN_STAT_SRC		0x10
#define DCP_CHAN_STAT_PACKET		0x08
#define DCP_CHAN_STAT_SETUP		0x04
#define DCP_CHAN_STAT_MISMATCH		0x02

/* hw packet control*/

#define DCP_PKT_PAYLOAD_KEY	(1<<11)
#define DCP_PKT_OTP_KEY		(1<<10)
#define DCP_PKT_CIPHER_INIT	(1<<9)
#define DCP_PKG_CIPHER_ENCRYPT	(1<<8)
#define DCP_PKT_CIPHER_ENABLE	(1<<5)
#define DCP_PKT_DECR_SEM	(1<<1)
#define DCP_PKT_CHAIN		(1<<2)
#define DCP_PKT_IRQ		1

#define DCP_PKT_MODE_CBC	(1<<4)
#define DCP_PKT_KEYSELECT_OTP	(0xFF<<8)

/* cipher flags */
#define DCP_ENC		0x0001
#define DCP_DEC		0x0002
#define DCP_ECB		0x0004
#define DCP_CBC		0x0008
#define DCP_CBC_INIT	0x0010
#define DCP_NEW_KEY	0x0040
#define DCP_OTP_KEY	0x0080
#define DCP_AES		0x1000

/* DCP Flags */
#define DCP_FLAG_BUSY	0x01
#define DCP_FLAG_PRODUCING	0x02

/* clock defines */
#define CLOCK_ON	1
#define CLOCK_OFF	0

struct dcp_dev_req_ctx {
	int mode;
};

struct dcp_op {
	unsigned int		flags;
	u8			key[AES_KEYSIZE_128];
	int			keylen;

	struct ablkcipher_request	*req;
	struct crypto_ablkcipher	*fallback;

	uint32_t stat;
	uint32_t pkt1;
	uint32_t pkt2;
	struct ablkcipher_walk walk;
};

struct dcp_dev {
	struct device *dev;
	void __iomem *dcp_regs_base;

	int dcp_vmi_irq;
	int dcp_irq;

	spinlock_t queue_lock;
	struct crypto_queue queue;

	uint32_t pkt_produced;
	uint32_t pkt_consumed;

	struct dcp_hw_packet *hw_pkg[DCP_MAX_PKG];
	dma_addr_t hw_phys_pkg;

	/* [KEY][IV] Both with 16 Bytes */
	u8 *payload_base;
	dma_addr_t payload_base_dma;


	struct tasklet_struct	done_task;
	struct tasklet_struct	queue_task;
	struct timer_list	watchdog;

	unsigned long		flags;

	struct dcp_op *ctx;

	struct miscdevice dcp_bootstream_misc;
};

struct dcp_hw_packet {
	uint32_t next;
	uint32_t pkt1;
	uint32_t pkt2;
	uint32_t src;
	uint32_t dst;
	uint32_t size;
	uint32_t payload;
	uint32_t stat;
};

static struct dcp_dev *global_dev;

static inline u32 dcp_chan_reg(u32 reg, int chan)
{
	return reg + (chan) * 0x40;
}

static inline void dcp_write(struct dcp_dev *dev, u32 data, u32 reg)
{
	writel(data, dev->dcp_regs_base + reg);
}

static inline void dcp_set(struct dcp_dev *dev, u32 data, u32 reg)
{
	writel(data, dev->dcp_regs_base + (reg | 0x04));
}

static inline void dcp_clear(struct dcp_dev *dev, u32 data, u32 reg)
{
	writel(data, dev->dcp_regs_base + (reg | 0x08));
}

static inline void dcp_toggle(struct dcp_dev *dev, u32 data, u32 reg)
{
	writel(data, dev->dcp_regs_base + (reg | 0x0C));
}

static inline unsigned int dcp_read(struct dcp_dev *dev, u32 reg)
{
	return readl(dev->dcp_regs_base + reg);
}

static void dcp_dma_unmap(struct dcp_dev *dev, struct dcp_hw_packet *pkt)
{
	dma_unmap_page(dev->dev, pkt->src, pkt->size, DMA_TO_DEVICE);
	dma_unmap_page(dev->dev, pkt->dst, pkt->size, DMA_FROM_DEVICE);
	dev_dbg(dev->dev, "unmap packet %x", (unsigned int) pkt);
}

static int dcp_dma_map(struct dcp_dev *dev,
	struct ablkcipher_walk *walk, struct dcp_hw_packet *pkt)
{
	dev_dbg(dev->dev, "map packet %x", (unsigned int) pkt);
	/* align to length = 16 */
	pkt->size = walk->nbytes - (walk->nbytes % 16);

	pkt->src = dma_map_page(dev->dev, walk->src.page, walk->src.offset,
		pkt->size, DMA_TO_DEVICE);

	if (pkt->src == 0) {
		dev_err(dev->dev, "Unable to map src");
		return -ENOMEM;
	}

	pkt->dst = dma_map_page(dev->dev, walk->dst.page, walk->dst.offset,
		pkt->size, DMA_FROM_DEVICE);

	if (pkt->dst == 0) {
		dev_err(dev->dev, "Unable to map dst");
		dma_unmap_page(dev->dev, pkt->src, pkt->size, DMA_TO_DEVICE);
		return -ENOMEM;
	}

	return 0;
}

static void dcp_op_one(struct dcp_dev *dev, struct dcp_hw_packet *pkt,
			uint8_t last)
{
	struct dcp_op *ctx = dev->ctx;
	pkt->pkt1 = ctx->pkt1;
	pkt->pkt2 = ctx->pkt2;

	pkt->payload = (u32) dev->payload_base_dma;
	pkt->stat = 0;

	if (ctx->flags & DCP_CBC_INIT) {
		pkt->pkt1 |= DCP_PKT_CIPHER_INIT;
		ctx->flags &= ~DCP_CBC_INIT;
	}

	mod_timer(&dev->watchdog, jiffies + msecs_to_jiffies(500));
	pkt->pkt1 |= DCP_PKT_IRQ;
	if (!last)
		pkt->pkt1 |= DCP_PKT_CHAIN;

	dev->pkt_produced++;

	dcp_write(dev, 1,
		dcp_chan_reg(DCP_REG_CHAN_SEMA, USED_CHANNEL));
}

static void dcp_op_proceed(struct dcp_dev *dev)
{
	struct dcp_op *ctx = dev->ctx;
	struct dcp_hw_packet *pkt;

	while (ctx->walk.nbytes) {
		int err = 0;

		pkt = dev->hw_pkg[dev->pkt_produced % DCP_MAX_PKG];
		err = dcp_dma_map(dev, &ctx->walk, pkt);
		if (err) {
			dev->ctx->stat |= err;
			/* start timer to wait for already set up calls */
			mod_timer(&dev->watchdog,
				jiffies + msecs_to_jiffies(500));
			break;
		}


		err = ctx->walk.nbytes - pkt->size;
		ablkcipher_walk_done(dev->ctx->req, &dev->ctx->walk, err);

		dcp_op_one(dev, pkt, ctx->walk.nbytes == 0);
		/* we have to wait if no space is left in buffer */
		if (dev->pkt_produced - dev->pkt_consumed == DCP_MAX_PKG)
			break;
	}
	clear_bit(DCP_FLAG_PRODUCING, &dev->flags);
}

static void dcp_op_start(struct dcp_dev *dev, uint8_t use_walk)
{
	struct dcp_op *ctx = dev->ctx;

	if (ctx->flags & DCP_NEW_KEY) {
		memcpy(dev->payload_base, ctx->key, ctx->keylen);
		ctx->flags &= ~DCP_NEW_KEY;
	}

	ctx->pkt1 = 0;
	ctx->pkt1 |= DCP_PKT_CIPHER_ENABLE;
	ctx->pkt1 |= DCP_PKT_DECR_SEM;

	if (ctx->flags & DCP_OTP_KEY)
		ctx->pkt1 |= DCP_PKT_OTP_KEY;
	else
		ctx->pkt1 |= DCP_PKT_PAYLOAD_KEY;

	if (ctx->flags & DCP_ENC)
		ctx->pkt1 |= DCP_PKG_CIPHER_ENCRYPT;

	ctx->pkt2 = 0;
	if (ctx->flags & DCP_CBC)
		ctx->pkt2 |= DCP_PKT_MODE_CBC;

	dev->pkt_produced = 0;
	dev->pkt_consumed = 0;

	ctx->stat = 0;
	dcp_clear(dev, -1, dcp_chan_reg(DCP_REG_CHAN_STAT, USED_CHANNEL));
	dcp_write(dev, (u32) dev->hw_phys_pkg,
		dcp_chan_reg(DCP_REG_CHAN_PTR, USED_CHANNEL));

	set_bit(DCP_FLAG_PRODUCING, &dev->flags);

	if (use_walk) {
		ablkcipher_walk_init(&ctx->walk, ctx->req->dst,
				ctx->req->src, ctx->req->nbytes);
		ablkcipher_walk_phys(ctx->req, &ctx->walk);
		dcp_op_proceed(dev);
	} else {
		dcp_op_one(dev, dev->hw_pkg[0], 1);
		clear_bit(DCP_FLAG_PRODUCING, &dev->flags);
	}
}

static void dcp_done_task(unsigned long data)
{
	struct dcp_dev *dev = (struct dcp_dev *)data;
	struct dcp_hw_packet *last_packet;
	int fin;
	fin = 0;

	for (last_packet = dev->hw_pkg[(dev->pkt_consumed) % DCP_MAX_PKG];
		last_packet->stat == 1;
		last_packet =
			dev->hw_pkg[++(dev->pkt_consumed) % DCP_MAX_PKG]) {

		dcp_dma_unmap(dev, last_packet);
		last_packet->stat = 0;
		fin++;
	}
	/* the last call of this function already consumed this IRQ's packet */
	if (fin == 0)
		return;

	dev_dbg(dev->dev,
		"Packet(s) done with status %x; finished: %d, produced:%d, complete consumed: %d",
		dev->ctx->stat, fin, dev->pkt_produced, dev->pkt_consumed);

	last_packet = dev->hw_pkg[(dev->pkt_consumed - 1) % DCP_MAX_PKG];
	if (!dev->ctx->stat && last_packet->pkt1 & DCP_PKT_CHAIN) {
		if (!test_and_set_bit(DCP_FLAG_PRODUCING, &dev->flags))
			dcp_op_proceed(dev);
		return;
	}

	while (unlikely(dev->pkt_consumed < dev->pkt_produced)) {
		dcp_dma_unmap(dev,
			dev->hw_pkg[dev->pkt_consumed++ % DCP_MAX_PKG]);
	}

	if (dev->ctx->flags & DCP_OTP_KEY) {
		/* we used the miscdevice, no walk to finish */
		clear_bit(DCP_FLAG_BUSY, &dev->flags);
		return;
	}

	ablkcipher_walk_complete(&dev->ctx->walk);
	dev->ctx->req->base.complete(&dev->ctx->req->base,
			dev->ctx->stat);
	dev->ctx->req = NULL;
	/* in case there are other requests in the queue */
	tasklet_schedule(&dev->queue_task);
}

static void dcp_watchdog(unsigned long data)
{
	struct dcp_dev *dev = (struct dcp_dev *)data;
	dev->ctx->stat |= dcp_read(dev,
			dcp_chan_reg(DCP_REG_CHAN_STAT, USED_CHANNEL));

	dev_err(dev->dev, "Timeout, Channel status: %x", dev->ctx->stat);

	if (!dev->ctx->stat)
		dev->ctx->stat = -ETIMEDOUT;

	dcp_done_task(data);
}


static irqreturn_t dcp_common_irq(int irq, void *context)
{
	u32 msk;
	struct dcp_dev *dev = (struct dcp_dev *) context;

	del_timer(&dev->watchdog);

	msk = DCP_STAT_IRQ(dcp_read(dev, DCP_REG_STAT));
	dcp_clear(dev, msk, DCP_REG_STAT);
	if (msk == 0)
		return IRQ_NONE;

	dev->ctx->stat |= dcp_read(dev,
			dcp_chan_reg(DCP_REG_CHAN_STAT, USED_CHANNEL));

	if (msk & DCP_STAT_CHAN_1)
		tasklet_schedule(&dev->done_task);

	return IRQ_HANDLED;
}

static irqreturn_t dcp_vmi_irq(int irq, void *context)
{
	return dcp_common_irq(irq, context);
}

static irqreturn_t dcp_irq(int irq, void *context)
{
	return dcp_common_irq(irq, context);
}

static void dcp_crypt(struct dcp_dev *dev, struct dcp_op *ctx)
{
	dev->ctx = ctx;

	if ((ctx->flags & DCP_CBC) && ctx->req->info) {
		ctx->flags |= DCP_CBC_INIT;
		memcpy(dev->payload_base + AES_KEYSIZE_128,
			ctx->req->info, AES_KEYSIZE_128);
	}

	dcp_op_start(dev, 1);
}

static void dcp_queue_task(unsigned long data)
{
	struct dcp_dev *dev = (struct dcp_dev *) data;
	struct crypto_async_request *async_req, *backlog;
	struct crypto_ablkcipher *tfm;
	struct dcp_op *ctx;
	struct dcp_dev_req_ctx *rctx;
	struct ablkcipher_request *req;
	unsigned long flags;

	spin_lock_irqsave(&dev->queue_lock, flags);

	backlog = crypto_get_backlog(&dev->queue);
	async_req = crypto_dequeue_request(&dev->queue);

	spin_unlock_irqrestore(&dev->queue_lock, flags);

	if (!async_req)
		goto ret_nothing_done;

	if (backlog)
		backlog->complete(backlog, -EINPROGRESS);

	req = ablkcipher_request_cast(async_req);
	tfm = crypto_ablkcipher_reqtfm(req);
	rctx = ablkcipher_request_ctx(req);
	ctx = crypto_ablkcipher_ctx(tfm);

	if (!req->src || !req->dst)
		goto ret_nothing_done;

	ctx->flags |= rctx->mode;
	ctx->req = req;

	dcp_crypt(dev, ctx);

	return;

ret_nothing_done:
	clear_bit(DCP_FLAG_BUSY, &dev->flags);
}


static int dcp_cra_init(struct crypto_tfm *tfm)
{
	const char *name = tfm->__crt_alg->cra_name;
	struct dcp_op *ctx = crypto_tfm_ctx(tfm);

	tfm->crt_ablkcipher.reqsize = sizeof(struct dcp_dev_req_ctx);

	ctx->fallback = crypto_alloc_ablkcipher(name, 0,
				CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK);

	if (IS_ERR(ctx->fallback)) {
		dev_err(global_dev->dev, "Error allocating fallback algo %s\n",
			name);
		return PTR_ERR(ctx->fallback);
	}

	return 0;
}

static void dcp_cra_exit(struct crypto_tfm *tfm)
{
	struct dcp_op *ctx = crypto_tfm_ctx(tfm);

	if (ctx->fallback)
		crypto_free_ablkcipher(ctx->fallback);

	ctx->fallback = NULL;
}

/* async interface */
static int dcp_aes_setkey(struct crypto_ablkcipher *tfm, const u8 *key,
		unsigned int len)
{
	struct dcp_op *ctx = crypto_ablkcipher_ctx(tfm);
	unsigned int ret = 0;
	ctx->keylen = len;
	ctx->flags = 0;
	if (len == AES_KEYSIZE_128) {
		if (memcmp(ctx->key, key, AES_KEYSIZE_128)) {
			memcpy(ctx->key, key, len);
			ctx->flags |= DCP_NEW_KEY;
		}
		return 0;
	}

	ctx->fallback->base.crt_flags &= ~CRYPTO_TFM_REQ_MASK;
	ctx->fallback->base.crt_flags |=
		(tfm->base.crt_flags & CRYPTO_TFM_REQ_MASK);

	ret = crypto_ablkcipher_setkey(ctx->fallback, key, len);
	if (ret) {
		struct crypto_tfm *tfm_aux = crypto_ablkcipher_tfm(tfm);

		tfm_aux->crt_flags &= ~CRYPTO_TFM_RES_MASK;
		tfm_aux->crt_flags |=
			(ctx->fallback->base.crt_flags & CRYPTO_TFM_RES_MASK);
	}
	return ret;
}

static int dcp_aes_cbc_crypt(struct ablkcipher_request *req, int mode)
{
	struct dcp_dev_req_ctx *rctx = ablkcipher_request_ctx(req);
	struct dcp_dev *dev = global_dev;
	unsigned long flags;
	int err = 0;

	if (!IS_ALIGNED(req->nbytes, AES_BLOCK_SIZE))
		return -EINVAL;

	rctx->mode = mode;

	spin_lock_irqsave(&dev->queue_lock, flags);
	err = ablkcipher_enqueue_request(&dev->queue, req);
	spin_unlock_irqrestore(&dev->queue_lock, flags);

	flags = test_and_set_bit(DCP_FLAG_BUSY, &dev->flags);

	if (!(flags & DCP_FLAG_BUSY))
		tasklet_schedule(&dev->queue_task);

	return err;
}

static int dcp_aes_cbc_encrypt(struct ablkcipher_request *req)
{
	struct crypto_tfm *tfm =
		crypto_ablkcipher_tfm(crypto_ablkcipher_reqtfm(req));
	struct dcp_op *ctx = crypto_ablkcipher_ctx(
		crypto_ablkcipher_reqtfm(req));

	if (unlikely(ctx->keylen != AES_KEYSIZE_128)) {
		int err = 0;
		ablkcipher_request_set_tfm(req, ctx->fallback);
		err = crypto_ablkcipher_encrypt(req);
		ablkcipher_request_set_tfm(req, __crypto_ablkcipher_cast(tfm));
		return err;
	}

	return dcp_aes_cbc_crypt(req, DCP_AES | DCP_ENC | DCP_CBC);
}

static int dcp_aes_cbc_decrypt(struct ablkcipher_request *req)
{
	struct crypto_tfm *tfm =
		crypto_ablkcipher_tfm(crypto_ablkcipher_reqtfm(req));
	struct dcp_op *ctx = crypto_ablkcipher_ctx(
		crypto_ablkcipher_reqtfm(req));

	if (unlikely(ctx->keylen != AES_KEYSIZE_128)) {
		int err = 0;
		ablkcipher_request_set_tfm(req, ctx->fallback);
		err = crypto_ablkcipher_decrypt(req);
		ablkcipher_request_set_tfm(req, __crypto_ablkcipher_cast(tfm));
		return err;
	}
	return dcp_aes_cbc_crypt(req, DCP_AES | DCP_DEC | DCP_CBC);
}

static struct crypto_alg algs[] = {
	{
		.cra_name = "cbc(aes)",
		.cra_driver_name = "dcp-cbc-aes",
		.cra_alignmask = 3,
		.cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC |
			  CRYPTO_ALG_NEED_FALLBACK,
		.cra_blocksize = AES_KEYSIZE_128,
		.cra_type = &crypto_ablkcipher_type,
		.cra_priority = 300,
		.cra_u.ablkcipher = {
			.min_keysize =	AES_KEYSIZE_128,
			.max_keysize = AES_KEYSIZE_128,
			.setkey = dcp_aes_setkey,
			.encrypt = dcp_aes_cbc_encrypt,
			.decrypt = dcp_aes_cbc_decrypt,
			.ivsize = AES_KEYSIZE_128,
		}

	},
};

/* DCP bootstream verification interface: uses OTP key for crypto */
static int dcp_bootstream_open(struct inode *inode, struct file *file)
{
	file->private_data = container_of((file->private_data),
			struct dcp_dev, dcp_bootstream_misc);
	return 0;
}

static long dcp_bootstream_ioctl(struct file *file,
					 unsigned int cmd, unsigned long arg)
{
	struct dcp_dev *dev = (struct dcp_dev *) file->private_data;
	void __user *argp = (void __user *)arg;
	int ret;

	if (dev == NULL)
		return -EBADF;

	if (cmd != DBS_ENC && cmd != DBS_DEC)
		return -EINVAL;

	if (copy_from_user(dev->payload_base, argp, 16))
		return -EFAULT;

	if (test_and_set_bit(DCP_FLAG_BUSY, &dev->flags))
		return -EAGAIN;

	dev->ctx = kzalloc(sizeof(struct dcp_op), GFP_KERNEL);
	if (!dev->ctx) {
		dev_err(dev->dev,
			"cannot allocate context for OTP crypto");
		clear_bit(DCP_FLAG_BUSY, &dev->flags);
		return -ENOMEM;
	}

	dev->ctx->flags = DCP_AES | DCP_ECB | DCP_OTP_KEY | DCP_CBC_INIT;
	dev->ctx->flags |= (cmd == DBS_ENC) ? DCP_ENC : DCP_DEC;
	dev->hw_pkg[0]->src = dev->payload_base_dma;
	dev->hw_pkg[0]->dst = dev->payload_base_dma;
	dev->hw_pkg[0]->size = 16;

	dcp_op_start(dev, 0);

	while (test_bit(DCP_FLAG_BUSY, &dev->flags))
		cpu_relax();

	ret = dev->ctx->stat;
	if (!ret && copy_to_user(argp, dev->payload_base, 16))
		ret =  -EFAULT;

	kfree(dev->ctx);

	return ret;
}

static const struct file_operations dcp_bootstream_fops = {
	.owner =		THIS_MODULE,
	.unlocked_ioctl =	dcp_bootstream_ioctl,
	.open =			dcp_bootstream_open,
};

static int dcp_probe(struct platform_device *pdev)
{
	struct dcp_dev *dev = NULL;
	struct resource *r;
	int i, ret, j;

	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	global_dev = dev;
	dev->dev = &pdev->dev;

	platform_set_drvdata(pdev, dev);

	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!r) {
		dev_err(&pdev->dev, "failed to get IORESOURCE_MEM\n");
		return -ENXIO;
	}
	dev->dcp_regs_base = devm_ioremap(&pdev->dev, r->start,
					  resource_size(r));

	dcp_set(dev, DCP_CTRL_SFRST, DCP_REG_CTRL);
	udelay(10);
	dcp_clear(dev, DCP_CTRL_SFRST | DCP_CTRL_CLKGATE, DCP_REG_CTRL);

	dcp_write(dev, DCP_CTRL_GATHER_RES_WRITE |
		DCP_CTRL_ENABLE_CONTEXT_CACHE | DCP_CTRL_CH_IRQ_E_1,
		DCP_REG_CTRL);

	dcp_write(dev, DCP_CHAN_CTRL_ENABLE_1, DCP_REG_CHAN_CTRL);

	for (i = 0; i < 4; i++)
		dcp_clear(dev, -1, dcp_chan_reg(DCP_REG_CHAN_STAT, i));

	dcp_clear(dev, -1, DCP_REG_STAT);


	r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (!r) {
		dev_err(&pdev->dev, "can't get IRQ resource (0)\n");
		return -EIO;
	}
	dev->dcp_vmi_irq = r->start;
	ret = request_irq(dev->dcp_vmi_irq, dcp_vmi_irq, 0, "dcp", dev);
	if (ret != 0) {
		dev_err(&pdev->dev, "can't request_irq (0)\n");
		return -EIO;
	}

	r = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
	if (!r) {
		dev_err(&pdev->dev, "can't get IRQ resource (1)\n");
		ret = -EIO;
		goto err_free_irq0;
	}
	dev->dcp_irq = r->start;
	ret = request_irq(dev->dcp_irq, dcp_irq, 0, "dcp", dev);
	if (ret != 0) {
		dev_err(&pdev->dev, "can't request_irq (1)\n");
		ret = -EIO;
		goto err_free_irq0;
	}

	dev->hw_pkg[0] = dma_alloc_coherent(&pdev->dev,
			DCP_MAX_PKG * sizeof(struct dcp_hw_packet),
			&dev->hw_phys_pkg,
			GFP_KERNEL);
	if (!dev->hw_pkg[0]) {
		dev_err(&pdev->dev, "Could not allocate hw descriptors\n");
		ret = -ENOMEM;
		goto err_free_irq1;
	}

	for (i = 1; i < DCP_MAX_PKG; i++) {
		dev->hw_pkg[i - 1]->next = dev->hw_phys_pkg
				+ i * sizeof(struct dcp_hw_packet);
		dev->hw_pkg[i] = dev->hw_pkg[i - 1] + 1;
	}
	dev->hw_pkg[i - 1]->next = dev->hw_phys_pkg;


	dev->payload_base = dma_alloc_coherent(&pdev->dev, 2 * AES_KEYSIZE_128,
			&dev->payload_base_dma, GFP_KERNEL);
	if (!dev->payload_base) {
		dev_err(&pdev->dev, "Could not allocate memory for key\n");
		ret = -ENOMEM;
		goto err_free_hw_packet;
	}
	tasklet_init(&dev->queue_task, dcp_queue_task,
		(unsigned long) dev);
	tasklet_init(&dev->done_task, dcp_done_task,
		(unsigned long) dev);
	spin_lock_init(&dev->queue_lock);

	crypto_init_queue(&dev->queue, 10);

	init_timer(&dev->watchdog);
	dev->watchdog.function = &dcp_watchdog;
	dev->watchdog.data = (unsigned long)dev;

	dev->dcp_bootstream_misc.minor = MISC_DYNAMIC_MINOR,
	dev->dcp_bootstream_misc.name = "dcpboot",
	dev->dcp_bootstream_misc.fops = &dcp_bootstream_fops,
	ret = misc_register(&dev->dcp_bootstream_misc);
	if (ret != 0) {
		dev_err(dev->dev, "Unable to register misc device\n");
		goto err_free_key_iv;
	}

	for (i = 0; i < ARRAY_SIZE(algs); i++) {
		algs[i].cra_priority = 300;
		algs[i].cra_ctxsize = sizeof(struct dcp_op);
		algs[i].cra_module = THIS_MODULE;
		algs[i].cra_init = dcp_cra_init;
		algs[i].cra_exit = dcp_cra_exit;
		if (crypto_register_alg(&algs[i])) {
			dev_err(&pdev->dev, "register algorithm failed\n");
			ret = -ENOMEM;
			goto err_unregister;
		}
	}
	dev_notice(&pdev->dev, "DCP crypto enabled.!\n");

	return 0;

err_unregister:
	for (j = 0; j < i; j++)
		crypto_unregister_alg(&algs[j]);
err_free_key_iv:
	dma_free_coherent(&pdev->dev, 2 * AES_KEYSIZE_128, dev->payload_base,
			dev->payload_base_dma);
err_free_hw_packet:
	dma_free_coherent(&pdev->dev, DCP_MAX_PKG *
		sizeof(struct dcp_hw_packet), dev->hw_pkg[0],
		dev->hw_phys_pkg);
err_free_irq1:
	free_irq(dev->dcp_irq, dev);
err_free_irq0:
	free_irq(dev->dcp_vmi_irq, dev);

	return ret;
}

static int dcp_remove(struct platform_device *pdev)
{
	struct dcp_dev *dev;
	int j;
	dev = platform_get_drvdata(pdev);

	dma_free_coherent(&pdev->dev,
			DCP_MAX_PKG * sizeof(struct dcp_hw_packet),
			dev->hw_pkg[0],	dev->hw_phys_pkg);

	dma_free_coherent(&pdev->dev, 2 * AES_KEYSIZE_128, dev->payload_base,
			dev->payload_base_dma);

	free_irq(dev->dcp_irq, dev);
	free_irq(dev->dcp_vmi_irq, dev);

	tasklet_kill(&dev->done_task);
	tasklet_kill(&dev->queue_task);

	for (j = 0; j < ARRAY_SIZE(algs); j++)
		crypto_unregister_alg(&algs[j]);

	misc_deregister(&dev->dcp_bootstream_misc);

	return 0;
}

static struct of_device_id fs_dcp_of_match[] = {
	{	.compatible = "fsl-dcp"},
	{},
};

static struct platform_driver fs_dcp_driver = {
	.probe = dcp_probe,
	.remove = dcp_remove,
	.driver = {
		.name = "fsl-dcp",
		.owner = THIS_MODULE,
		.of_match_table = fs_dcp_of_match
	}
};

module_platform_driver(fs_dcp_driver);


MODULE_AUTHOR("Tobias Rauter <tobias.rauter@gmail.com>");
MODULE_DESCRIPTION("Freescale DCP Crypto Driver");
MODULE_LICENSE("GPL");