misc: tegra-cryptodev: device node to access tegra aes hw
Varun Wadekar [Fri, 11 Feb 2011 10:02:14 +0000 (02:02 -0800)]
/dev/tegra-crypto node to access tegra aes hw from the
user space. currently ecb/cbc/ansi_x9.31 modes are supported

Original-Change-Id: I8d9e48d4b139e9c2e26a885773fb2f792fb6ca87
Signed-off-by: Varun Wadekar <vwadekar@nvidia.com>
Reviewed-on: http://git-master/r/16046
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>

Rebase-Id: R1e3e1f059255adb13d39c4332f6cdfb3f3c8c3f0

drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/tegra-cryptodev.c [new file with mode: 0644]
drivers/misc/tegra-cryptodev.h [new file with mode: 0644]

index 228fba4..48a9a1f 100644 (file)
@@ -512,6 +512,12 @@ config BCM4329_RFKILL
        ---help---
        Adds BCM4329 RFKILL driver for Broadcom BCM4329 chipset
 
+config TEGRA_CRYPTO_DEV
+       bool "Device node to access tegra aes hardware"
+       ---help---
+       Dev node /dev/tegra-crypto in order to get access to tegra aes
+       hardware from user space
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
index b0091d0..99375a0 100644 (file)
@@ -51,3 +51,4 @@ obj-$(CONFIG_ALTERA_STAPL)    +=altera-stapl/
 obj-$(CONFIG_MAX8997_MUIC)     += max8997-muic.o
 obj-$(CONFIG_SENSORS_NCT1008)  += nct1008.o
 obj-$(CONFIG_BCM4329_RFKILL)   += bcm4329_rfkill.o
+obj-$(CONFIG_TEGRA_CRYPTO_DEV) += tegra-cryptodev.o
diff --git a/drivers/misc/tegra-cryptodev.c b/drivers/misc/tegra-cryptodev.c
new file mode 100644 (file)
index 0000000..a18f59e
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * drivers/misc/tegra-cryptodev.c
+ *
+ * crypto dev node for NVIDIA tegra aes hardware
+ *
+ * Copyright (c) 2010, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/crypto.h>
+#include <linux/scatterlist.h>
+#include <linux/uaccess.h>
+#include <crypto/rng.h>
+
+#include "tegra-cryptodev.h"
+
+#define NBUFS 2
+
+struct tegra_crypto_ctx {
+       struct crypto_ablkcipher *ecb_tfm;
+       struct crypto_ablkcipher *cbc_tfm;
+       struct crypto_rng *rng;
+       u8 seed[TEGRA_CRYPTO_RNG_SEED_SIZE];
+       int use_ssk;
+};
+
+static int alloc_bufs(unsigned long *buf[NBUFS])
+{
+       int i;
+
+       for (i = 0; i < NBUFS; i++) {
+               buf[i] = (void *)__get_free_page(GFP_KERNEL);
+               if (!buf[i])
+                       goto err_free_buf;
+       }
+
+       return 0;
+
+err_free_buf:
+       while (i-- > 0)
+               free_page((unsigned long)buf[i]);
+
+       return -ENOMEM;
+}
+
+static void free_bufs(unsigned long *buf[NBUFS])
+{
+       int i;
+
+       for (i = 0; i < NBUFS; i++)
+               free_page((unsigned long)buf[i]);
+}
+
+static int tegra_crypto_dev_open(struct inode *inode, struct file *filp)
+{
+       struct tegra_crypto_ctx *ctx;
+       int ret = 0;
+
+       ctx = kzalloc(sizeof(struct tegra_crypto_ctx), GFP_KERNEL);
+       if (!ctx) {
+               pr_err("no memory for context\n");
+               return -ENOMEM;
+       }
+
+       ctx->ecb_tfm = crypto_alloc_ablkcipher("ecb-aes-tegra",
+               CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, 0);
+       if (IS_ERR(ctx->ecb_tfm)) {
+               pr_err("Failed to load transform for ecb-aes-tegra: %ld\n",
+                       PTR_ERR(ctx->ecb_tfm));
+               ret = PTR_ERR(ctx->ecb_tfm);
+               goto fail_ecb;
+       }
+
+       ctx->cbc_tfm = crypto_alloc_ablkcipher("cbc-aes-tegra",
+               CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, 0);
+       if (IS_ERR(ctx->cbc_tfm)) {
+               pr_err("Failed to load transform for cbc-aes-tegra: %ld\n",
+                       PTR_ERR(ctx->cbc_tfm));
+               ret = PTR_ERR(ctx->cbc_tfm);
+               goto fail_cbc;
+       }
+
+       ctx->rng = crypto_alloc_rng("rng-aes-tegra", CRYPTO_ALG_TYPE_RNG, 0);
+       if (IS_ERR(ctx->rng)) {
+               pr_err("Failed to load transform for tegra rng: %ld\n",
+                       PTR_ERR(ctx->rng));
+               ret = PTR_ERR(ctx->rng);
+               goto fail_rng;
+       }
+
+       filp->private_data = ctx;
+       return ret;
+
+fail_rng:
+       crypto_free_ablkcipher(ctx->cbc_tfm);
+
+fail_cbc:
+       crypto_free_ablkcipher(ctx->ecb_tfm);
+
+fail_ecb:
+       kfree(ctx);
+       return ret;
+}
+
+static int tegra_crypto_dev_release(struct inode *inode, struct file *filp)
+{
+       struct tegra_crypto_ctx *ctx = filp->private_data;
+
+       crypto_free_ablkcipher(ctx->ecb_tfm);
+       crypto_free_ablkcipher(ctx->cbc_tfm);
+       crypto_free_rng(ctx->rng);
+       kfree(ctx);
+       filp->private_data = NULL;
+       return 0;
+}
+
+static int process_crypt_req(struct tegra_crypto_ctx *ctx, struct tegra_crypt_req *crypt_req)
+{
+       struct crypto_ablkcipher *tfm;
+       struct ablkcipher_request *req = NULL;
+       struct scatterlist in_sg;
+       struct scatterlist out_sg;
+       unsigned long *xbuf[NBUFS];
+       int ret = 0, size = 0;
+       unsigned long total = 0;
+
+       if (crypt_req->op & TEGRA_CRYPTO_ECB) {
+               req = ablkcipher_request_alloc(ctx->ecb_tfm, GFP_KERNEL);
+               tfm = ctx->ecb_tfm;
+       } else {
+               req = ablkcipher_request_alloc(ctx->cbc_tfm, GFP_KERNEL);
+               tfm = ctx->cbc_tfm;
+       }
+       if (!req) {
+               pr_err("%s: Failed to allocate request\n", __func__);
+               return -ENOMEM;
+       }
+
+       if ((crypt_req->keylen < 0) || (crypt_req->keylen > AES_MAX_KEY_SIZE))
+               return -EINVAL;
+
+       crypto_ablkcipher_clear_flags(tfm, ~0);
+
+       if (!ctx->use_ssk) {
+               ret = crypto_ablkcipher_setkey(tfm, crypt_req->key,
+                       crypt_req->keylen);
+               if (ret < 0) {
+                       pr_err("setkey failed");
+                       goto process_req_out;
+               }
+       }
+
+       ret = alloc_bufs(xbuf);
+       if (ret < 0) {
+               pr_err("alloc_bufs failed");
+               goto process_req_out;
+       }
+
+       total = crypt_req->plaintext_sz;
+       while (total > 0) {
+               size = min(total, PAGE_SIZE);
+               ret = copy_from_user((void *)xbuf[0],
+                       (void __user *)crypt_req->plaintext, size);
+               if (ret < 0) {
+                       pr_debug("%s: copy_from_user failed (%d)\n", __func__, ret);
+                       goto process_req_buf_out;
+               }
+               sg_init_one(&in_sg, xbuf[0], size);
+               sg_init_one(&out_sg, xbuf[1], size);
+               ablkcipher_request_set_crypt(req, &in_sg,
+                       &out_sg, size, crypt_req->iv);
+               req->base.complete = NULL;
+               ret = crypt_req->encrypt ?
+                       crypto_ablkcipher_encrypt(req) :
+                       crypto_ablkcipher_decrypt(req);
+               if (ret < 0) {
+                       pr_debug("%scrypt failed (%d)\n",
+                               crypt_req->encrypt ? "en" : "de", ret);
+                       goto process_req_buf_out;
+               }
+
+               ret = copy_to_user((void __user *)crypt_req->result,
+                       (const void *)xbuf[1], size);
+               if (ret < 0)
+                       goto process_req_buf_out;
+
+               total -= size;
+               crypt_req->result += size;
+               crypt_req->plaintext += size;
+       }
+
+process_req_buf_out:
+       free_bufs(xbuf);
+process_req_out:
+       ablkcipher_request_free(req);
+
+       return ret;
+}
+
+static long tegra_crypto_dev_ioctl(struct file *filp,
+       unsigned int ioctl_num, unsigned long arg)
+{
+       struct tegra_crypto_ctx *ctx = filp->private_data;
+       struct tegra_crypt_req crypt_req;
+       struct tegra_rng_req rng_req;
+       char rng[TEGRA_CRYPTO_RNG_SIZE];
+       int ret = 0;
+
+       switch (ioctl_num) {
+       case TEGRA_CRYPTO_IOCTL_NEED_SSK:
+               ctx->use_ssk = (int)arg;
+               break;
+       case TEGRA_CRYPTO_IOCTL_PROCESS_REQ:
+               ret = copy_from_user(&crypt_req, (void __user *)arg, sizeof(crypt_req));
+               if (ret < 0) {
+                       pr_debug("%s: copy_from_user fail(%d)\n", __func__, ret);
+                       break;
+               }
+
+               ret = process_crypt_req(ctx, &crypt_req);
+               break;
+
+       case TEGRA_CRYPTO_IOCTL_SET_SEED:
+               if (copy_from_user(&rng_req, (void __user *)arg, sizeof(rng_req)))
+                       return -EFAULT;
+
+               memcpy(ctx->seed, rng_req.seed, TEGRA_CRYPTO_RNG_SEED_SIZE);
+
+               ret = crypto_rng_reset(ctx->rng, ctx->seed,
+                       crypto_rng_seedsize(ctx->rng));
+               break;
+       case TEGRA_CRYPTO_IOCTL_GET_RANDOM:
+               if (copy_from_user(&rng_req, (void __user *)arg, sizeof(rng_req)))
+                       return -EFAULT;
+
+               ret = crypto_rng_get_bytes(ctx->rng, rng,
+                       rng_req.nbytes);
+
+               if (ret != rng_req.nbytes) {
+                       pr_debug("rng failed");
+                       ret = -ENODATA;
+                       goto rng_out;
+               }
+
+               ret = copy_to_user((void __user *)rng_req.rdata,
+                       (const void *)rng, rng_req.nbytes);
+               ret = (ret < 0) ? -ENODATA : 0;
+rng_out:
+               break;
+
+
+       default:
+               pr_debug("invalid ioctl code(%d)", ioctl_num);
+               ret = -EINVAL;
+       }
+       return ret;
+}
+
+struct file_operations tegra_crypto_fops = {
+       .owner = THIS_MODULE,
+       .open = tegra_crypto_dev_open,
+       .release = tegra_crypto_dev_release,
+       .unlocked_ioctl = tegra_crypto_dev_ioctl,
+};
+
+struct miscdevice tegra_crypto_device = {
+       .minor = MISC_DYNAMIC_MINOR,
+       .name = "tegra-crypto",
+       .fops = &tegra_crypto_fops,
+};
+
+static int __init tegra_crypto_dev_init(void)
+{
+       return misc_register(&tegra_crypto_device);
+}
+
+late_initcall(tegra_crypto_dev_init);
+
+MODULE_DESCRIPTION("Tegra AES hw device node.");
+MODULE_AUTHOR("NVIDIA Corporation");
+MODULE_LICENSE("GPLv2");
diff --git a/drivers/misc/tegra-cryptodev.h b/drivers/misc/tegra-cryptodev.h
new file mode 100644 (file)
index 0000000..ed62a52
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2010, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __TEGRA_CRYPTODEV_H
+#define __TEGRA_CRYPTODEV_H
+
+#include <crypto/aes.h>
+
+#include <asm-generic/ioctl.h>
+
+/* ioctl arg = 1 if you want to use ssk. arg = 0 to use normal key */
+#define TEGRA_CRYPTO_IOCTL_NEED_SSK    _IOWR(0x98, 100, int)
+#define TEGRA_CRYPTO_IOCTL_PROCESS_REQ _IOWR(0x98, 101, int*)
+#define TEGRA_CRYPTO_IOCTL_SET_SEED    _IOWR(0x98, 102, int*)
+#define TEGRA_CRYPTO_IOCTL_GET_RANDOM  _IOWR(0x98, 103, int*)
+
+#define TEGRA_CRYPTO_MAX_KEY_SIZE      AES_MAX_KEY_SIZE
+#define TEGRA_CRYPTO_IV_SIZE   AES_BLOCK_SIZE
+#define DEFAULT_RNG_BLK_SZ     16
+
+/* the seed consists of 16 bytes of key + 16 bytes of init vector */
+#define TEGRA_CRYPTO_RNG_SEED_SIZE     AES_KEYSIZE_128 + DEFAULT_RNG_BLK_SZ
+#define TEGRA_CRYPTO_RNG_SIZE  SZ_16
+
+/* encrypt/decrypt operations */
+#define TEGRA_CRYPTO_ECB       BIT(0)
+#define TEGRA_CRYPTO_CBC       BIT(1)
+#define TEGRA_CRYPTO_RNG       BIT(2)
+
+/* a pointer to this struct needs to be passed to:
+ * TEGRA_CRYPTO_IOCTL_PROCESS_REQ
+ */
+struct tegra_crypt_req {
+       int op; /* e.g. TEGRA_CRYPTO_ECB */
+       bool encrypt;
+       char key[TEGRA_CRYPTO_MAX_KEY_SIZE];
+       int keylen;
+       char iv[TEGRA_CRYPTO_IV_SIZE];
+       int ivlen;
+       u8 *plaintext;
+       int plaintext_sz;
+       u8 *result;
+};
+
+/* pointer to this struct should be passed to:
+ * TEGRA_CRYPTO_IOCTL_SET_SEED
+ * TEGRA_CRYPTO_IOCTL_GET_RANDOM
+ */
+struct tegra_rng_req {
+       u8 seed[TEGRA_CRYPTO_RNG_SEED_SIZE];
+       u8 *rdata; /* random generated data */
+       int nbytes; /* random data length */
+};
+
+#endif