mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
ff621c3bd8
PD#SWPL-138376 Problem: When there are multiple process accessing crypto device at the same time, some of the request might be dropped and causing process failed to exit. Solution: Move mutex lock to properly protect HW operation Verify: S905C3 Change-Id: Ie12b1f46e87dcbbaf5df4f780711dcebc9c87e75 Signed-off-by: Matthew Shyu <matthew.shyu@amlogic.com>
2155 lines
52 KiB
C
2155 lines
52 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
/* A part of the code is taken from crypto dev
|
|
* https://github.com/cryptodev-linux/cryptodev-linux.git
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/err.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/device.h>
|
|
#include <linux/init.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/random.h>
|
|
#include <linux/crypto.h>
|
|
#include <crypto/aes.h>
|
|
#include <crypto/sm4.h>
|
|
#include <crypto/des.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/arm-smccc.h>
|
|
#include <linux/amlogic/aml_crypto.h>
|
|
#include "aml-crypto-dma.h"
|
|
#include <linux/compat.h>
|
|
|
|
#define USE_BUSY_POLLING (0)
|
|
#define MAX_IV_SIZE (32)
|
|
#define CRYPTO_OPERATION_CFG (0x8200002A)
|
|
#define SET_S17_M2M_CFG (0x0)
|
|
|
|
#define TEST_PHYSICAL_PROCESS (0)
|
|
#if TEST_PHYSICAL_PROCESS
|
|
dma_addr_t g_src_dma[MAX_CRYPTO_BUFFERS];
|
|
u8 *g_src[MAX_CRYPTO_BUFFERS];
|
|
u8 *g_tmp_src_addr[MAX_CRYPTO_BUFFERS];
|
|
dma_addr_t g_dst_dma[MAX_CRYPTO_BUFFERS];
|
|
u8 *g_dst[MAX_CRYPTO_BUFFERS];
|
|
u8 *g_tmp_dst_addr[MAX_CRYPTO_BUFFERS];
|
|
#endif
|
|
|
|
struct aml_crypto_dev {
|
|
struct device *dev;
|
|
int irq;
|
|
u32 err;
|
|
u32 dma_busy;
|
|
struct mutex lock; /* lock to protect crypto device */
|
|
|
|
u32 thread;
|
|
u32 status;
|
|
|
|
u32 algo_cap;
|
|
|
|
struct task_struct *processing;
|
|
};
|
|
|
|
struct aml_crypto_dev *crypto_dd;
|
|
|
|
struct meson_crypto_dev_data {
|
|
u32 status;
|
|
u32 algo_cap;
|
|
};
|
|
|
|
struct meson_crypto_dev_data meson_sc2_data = {
|
|
.status = TXLX_DMA_STS0,
|
|
.algo_cap = (CAP_AES | CAP_TDES | CAP_DES),
|
|
};
|
|
|
|
struct meson_crypto_dev_data meson_s4d_data = {
|
|
.status = TXLX_DMA_STS0,
|
|
.algo_cap = (CAP_AES | CAP_TDES | CAP_DES | CAP_S17),
|
|
};
|
|
|
|
struct meson_crypto_dev_data meson_s5_data = {
|
|
.status = TXLX_DMA_STS0,
|
|
.algo_cap = (CAP_AES | CAP_TDES | CAP_DES | CAP_SM4),
|
|
};
|
|
|
|
struct cipher_data {
|
|
int init; /* 0 if uninitialized */
|
|
int cipher;
|
|
int blocksize;
|
|
int ivsize;
|
|
int kte;
|
|
int keylen;
|
|
int op_mode;
|
|
int crypt_mode;
|
|
};
|
|
|
|
struct crypto_session {
|
|
struct list_head entry;
|
|
struct mutex sem; /* lock to protect crypto session */
|
|
u32 sid;
|
|
struct cipher_data cdata;
|
|
};
|
|
|
|
struct fcrypt {
|
|
struct list_head list;
|
|
struct mutex sem; /* lock to protect fcrypt list */
|
|
};
|
|
|
|
struct crypt_priv {
|
|
struct fcrypt fcrypt;
|
|
};
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
struct compat_crypt_mem {
|
|
__u32 length;
|
|
compat_uptr_t addr;
|
|
};
|
|
|
|
struct compat_crypt_op {
|
|
__u32 ses; /* session identifier */
|
|
__u8 op; /* OP_ENCRYPT or OP_DECRYPT */
|
|
__u8 src_phys; /* set if src is in physical addr */
|
|
__u8 dst_phys; /* set if dst is in physical addr */
|
|
__u8 ivlen; /* length of IV */
|
|
compat_uptr_t __user iv;
|
|
compat_uptr_t __user param; /* extra parameters for algorithm */
|
|
__u16 param_len;
|
|
__u8 num_src_bufs;
|
|
__u8 num_dst_bufs;
|
|
__u32 reserved;
|
|
struct compat_crypt_mem src[MAX_CRYPTO_BUFFERS]; /* source data */
|
|
struct compat_crypt_mem dst[MAX_CRYPTO_BUFFERS]; /* output data */
|
|
};
|
|
|
|
#define DO_CRYPTO_COMPAT _IOWR('a', 2, struct compat_crypt_op)
|
|
#endif
|
|
|
|
struct kernel_crypt_op {
|
|
struct crypt_op cop;
|
|
|
|
__u8 *param;
|
|
__u8 iv[MAX_IV_SIZE];
|
|
u16 param_len;
|
|
u8 ivlen;
|
|
};
|
|
|
|
static noinline int call_smc(u64 func_id, u64 arg0, u64 arg1, u64 arg2)
|
|
{
|
|
struct arm_smccc_res res;
|
|
|
|
arm_smccc_smc((unsigned long)func_id,
|
|
(unsigned long)arg0,
|
|
(unsigned long)arg1,
|
|
(unsigned long)arg2,
|
|
0, 0, 0, 0, &res);
|
|
return res.a0;
|
|
}
|
|
|
|
#if !USE_BUSY_POLLING
|
|
static irqreturn_t aml_crypto_dev_irq(int irq, void *dev_id)
|
|
{
|
|
struct aml_crypto_dev *dd = dev_id;
|
|
struct device *dev = dd->dev;
|
|
u8 status = aml_read_crypto_reg(dd->status);
|
|
|
|
if (status) {
|
|
if (status == 0x1)
|
|
dev_err(dev, "irq overwrite\n");
|
|
if (dd->dma_busy) {
|
|
if (status & DMA_STATUS_KEY_ERROR) {
|
|
dev_err(dev, "crypto device failed to fetch key\n");
|
|
dd->err = DMA_STATUS_KEY_ERROR;
|
|
} else {
|
|
dd->err = 0;
|
|
}
|
|
aml_write_crypto_reg(dd->status, 0xf);
|
|
wake_up_process(dd->processing);
|
|
return IRQ_HANDLED;
|
|
} else {
|
|
return IRQ_NONE;
|
|
}
|
|
}
|
|
|
|
return IRQ_NONE;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id aml_crypto_dev_dt_match[] = {
|
|
{ .compatible = "amlogic,crypto_sc2",
|
|
.data = &meson_sc2_data,
|
|
},
|
|
{ .compatible = "amlogic,crypto_s4d",
|
|
.data = &meson_s4d_data,
|
|
},
|
|
{ .compatible = "amlogic,crypto_s5",
|
|
.data = &meson_s5_data,
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, aml_crypto_dev_dt_match);
|
|
#else
|
|
#define aml_crypto_dev_dt_match NULL
|
|
#endif
|
|
|
|
static int crypto_create_session(struct fcrypt *fcr, struct session_op *sop)
|
|
{
|
|
struct crypto_session *ses_new = NULL;
|
|
struct crypto_session *ses_ptr = NULL;
|
|
u32 retry = 0;
|
|
int done = 0;
|
|
int rc = 0;
|
|
|
|
if (sop->cipher >= CRYPTO_OP_MAX || sop->cipher == CRYPTO_OP_INVALID)
|
|
return -EINVAL;
|
|
if (sop->keylen > AES_MAX_KEY_SIZE)
|
|
return -EINVAL;
|
|
|
|
ses_new = kzalloc(sizeof(*ses_new), GFP_KERNEL);
|
|
if (!ses_new)
|
|
return -ENOMEM;
|
|
|
|
get_random_bytes(&ses_new->sid, sizeof(ses_new->sid));
|
|
mutex_init(&ses_new->sem);
|
|
|
|
ses_new->cdata.cipher = sop->cipher;
|
|
switch (ses_new->cdata.cipher) {
|
|
case CRYPTO_OP_DES_ECB:
|
|
ses_new->cdata.op_mode = OP_MODE_ECB;
|
|
break;
|
|
case CRYPTO_OP_DES_CBC:
|
|
ses_new->cdata.op_mode = OP_MODE_CBC;
|
|
break;
|
|
case CRYPTO_OP_TDES_ECB:
|
|
ses_new->cdata.op_mode = OP_MODE_ECB;
|
|
break;
|
|
case CRYPTO_OP_TDES_CBC:
|
|
ses_new->cdata.op_mode = OP_MODE_CBC;
|
|
break;
|
|
case CRYPTO_OP_AES_ECB:
|
|
ses_new->cdata.op_mode = OP_MODE_ECB;
|
|
break;
|
|
case CRYPTO_OP_AES_CBC:
|
|
ses_new->cdata.op_mode = OP_MODE_CBC;
|
|
break;
|
|
case CRYPTO_OP_AES_CTR:
|
|
ses_new->cdata.op_mode = OP_MODE_CTR;
|
|
break;
|
|
case CRYPTO_OP_S17_ECB:
|
|
ses_new->cdata.op_mode = OP_MODE_ECB;
|
|
break;
|
|
case CRYPTO_OP_S17_CBC:
|
|
ses_new->cdata.op_mode = OP_MODE_CBC;
|
|
break;
|
|
case CRYPTO_OP_S17_CTR:
|
|
ses_new->cdata.op_mode = OP_MODE_CTR;
|
|
break;
|
|
case CRYPTO_OP_SM4_ECB:
|
|
ses_new->cdata.op_mode = OP_MODE_ECB;
|
|
break;
|
|
case CRYPTO_OP_SM4_CBC:
|
|
ses_new->cdata.op_mode = OP_MODE_CBC;
|
|
break;
|
|
case CRYPTO_OP_SM4_CTR:
|
|
ses_new->cdata.op_mode = OP_MODE_CTR;
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
switch (ses_new->cdata.cipher) {
|
|
case CRYPTO_OP_DES_ECB:
|
|
fallthrough;
|
|
case CRYPTO_OP_DES_CBC:
|
|
ses_new->cdata.blocksize = DES_BLOCK_SIZE;
|
|
ses_new->cdata.ivsize = DES_BLOCK_SIZE;
|
|
if (sop->keylen != DES_BLOCK_SIZE) {
|
|
dbgp(2, "invalid keysize: %d\n", sop->keylen);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
} else {
|
|
ses_new->cdata.keylen = sop->keylen;
|
|
}
|
|
ses_new->cdata.crypt_mode = MODE_DES;
|
|
if (!(crypto_dd->algo_cap & CAP_DES)) {
|
|
dbgp(2, "unsupported algo: %d\n",
|
|
ses_new->cdata.cipher);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
case CRYPTO_OP_TDES_ECB:
|
|
fallthrough;
|
|
case CRYPTO_OP_TDES_CBC:
|
|
ses_new->cdata.blocksize = DES_BLOCK_SIZE;
|
|
ses_new->cdata.ivsize = DES_BLOCK_SIZE;
|
|
if (sop->keylen != DES_BLOCK_SIZE * 2 &&
|
|
sop->keylen != DES_BLOCK_SIZE * 3) {
|
|
dbgp(2, "invalid keysize: %d\n", sop->keylen);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
} else {
|
|
ses_new->cdata.keylen = sop->keylen;
|
|
}
|
|
ses_new->cdata.crypt_mode =
|
|
(ses_new->cdata.keylen == DES_BLOCK_SIZE * 2) ?
|
|
MODE_TDES_2K : MODE_TDES_3K;
|
|
if (!(crypto_dd->algo_cap & CAP_TDES)) {
|
|
dbgp(2, "unsupported algo: %d\n",
|
|
ses_new->cdata.cipher);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
case CRYPTO_OP_AES_ECB:
|
|
fallthrough;
|
|
case CRYPTO_OP_AES_CBC:
|
|
fallthrough;
|
|
case CRYPTO_OP_AES_CTR:
|
|
ses_new->cdata.blocksize = AES_BLOCK_SIZE;
|
|
ses_new->cdata.ivsize = AES_BLOCK_SIZE;
|
|
/* HW only supports AES-128 and AES-256 */
|
|
if (sop->keylen != AES_BLOCK_SIZE &&
|
|
sop->keylen != AES_BLOCK_SIZE * 2) {
|
|
dbgp(2, "invalid keysize: %d\n", sop->keylen);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
} else {
|
|
ses_new->cdata.keylen = sop->keylen;
|
|
}
|
|
ses_new->cdata.crypt_mode = (ses_new->cdata.keylen ==
|
|
AES_BLOCK_SIZE) ? MODE_AES128 :
|
|
MODE_AES256;
|
|
if (!(crypto_dd->algo_cap & CAP_AES)) {
|
|
dbgp(2, "unsupported algo: %d\n",
|
|
ses_new->cdata.cipher);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
case CRYPTO_OP_SM4_ECB:
|
|
fallthrough;
|
|
case CRYPTO_OP_SM4_CBC:
|
|
fallthrough;
|
|
case CRYPTO_OP_SM4_CTR:
|
|
ses_new->cdata.blocksize = SM4_BLOCK_SIZE;
|
|
ses_new->cdata.ivsize = SM4_BLOCK_SIZE;
|
|
if (sop->keylen != SM4_BLOCK_SIZE) {
|
|
dbgp(2, "invalid keysize: %d\n", sop->keylen);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
} else {
|
|
ses_new->cdata.keylen = sop->keylen;
|
|
}
|
|
ses_new->cdata.crypt_mode = MODE_SM4;
|
|
if (!(crypto_dd->algo_cap & CAP_SM4)) {
|
|
dbgp(2, "unsupported algo: %d\n",
|
|
ses_new->cdata.cipher);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
case CRYPTO_OP_S17_ECB:
|
|
fallthrough;
|
|
case CRYPTO_OP_S17_CBC:
|
|
fallthrough;
|
|
case CRYPTO_OP_S17_CTR:
|
|
if (sop->keylen != 32) {
|
|
dbgp(2, "invalid keysize: %d\n", sop->keylen);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
} else {
|
|
ses_new->cdata.keylen = sop->keylen;
|
|
}
|
|
ses_new->cdata.blocksize = 16;
|
|
ses_new->cdata.ivsize = 32;
|
|
ses_new->cdata.crypt_mode = MODE_S17;
|
|
if (!(crypto_dd->algo_cap & CAP_S17)) {
|
|
dbgp(2, "unsupported algo: %d\n",
|
|
ses_new->cdata.cipher);
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
ses_new->cdata.kte = sop->kte;
|
|
ses_new->cdata.init = 1;
|
|
|
|
mutex_lock(&fcr->sem);
|
|
|
|
do {
|
|
list_for_each_entry(ses_ptr, &fcr->list, entry) {
|
|
/* Check for duplicate SID */
|
|
if (unlikely(ses_new->sid == ses_ptr->sid)) {
|
|
get_random_bytes(&ses_new->sid,
|
|
sizeof(ses_new->sid));
|
|
retry++;
|
|
break;
|
|
}
|
|
}
|
|
done = 1;
|
|
} while (retry < 20 && !done);
|
|
|
|
if (!done) {
|
|
dbgp(2, "failed to find a good session ID\n");
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
list_add(&ses_new->entry, &fcr->list);
|
|
mutex_unlock(&fcr->sem);
|
|
|
|
/* Fill in some values for the user. */
|
|
sop->ses = ses_new->sid;
|
|
return rc;
|
|
|
|
error:
|
|
kfree(ses_new);
|
|
return rc;
|
|
}
|
|
|
|
static int crypto_finish_session(struct fcrypt *fcr, u32 sid)
|
|
{
|
|
struct crypto_session *tmp, *ses_ptr;
|
|
struct list_head *head;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&fcr->sem);
|
|
head = &fcr->list;
|
|
list_for_each_entry_safe(ses_ptr, tmp, head, entry) {
|
|
if (ses_ptr->sid == sid) {
|
|
list_del(&ses_ptr->entry);
|
|
mutex_destroy(&ses_ptr->sem);
|
|
kfree(ses_ptr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (unlikely(!ses_ptr)) {
|
|
dbgp(2, "Session with sid=0x%08X not found!", sid);
|
|
ret = -EINVAL;
|
|
}
|
|
mutex_unlock(&fcr->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline void crypto_put_session(struct crypto_session *ses_ptr)
|
|
{
|
|
mutex_unlock(&ses_ptr->sem);
|
|
}
|
|
|
|
/* Look up session by session ID. The returned session is locked. */
|
|
struct crypto_session *crypto_get_session_by_sid(struct fcrypt *fcr,
|
|
u32 sid)
|
|
{
|
|
struct crypto_session *ses_ptr, *retval = NULL;
|
|
|
|
if (unlikely(!fcr))
|
|
return NULL;
|
|
|
|
mutex_lock(&fcr->sem);
|
|
list_for_each_entry(ses_ptr, &fcr->list, entry) {
|
|
if (ses_ptr->sid == sid) {
|
|
mutex_lock(&ses_ptr->sem);
|
|
retval = ses_ptr;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&fcr->sem);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int fill_kcop_from_cop(struct kernel_crypt_op *kcop, struct fcrypt *fcr)
|
|
{
|
|
struct crypt_op *cop = &kcop->cop;
|
|
struct crypto_session *ses_ptr;
|
|
int rc;
|
|
|
|
/* this also enters ses_ptr->sem */
|
|
ses_ptr = crypto_get_session_by_sid(fcr, cop->ses);
|
|
if (unlikely(!ses_ptr)) {
|
|
dbgp(2, "invalid session ID=0x%08X", cop->ses);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (unlikely(cop->iv && cop->ivlen < ses_ptr->cdata.ivsize)) {
|
|
dbgp(2, "invalid ivlen = %d", cop->ivlen);
|
|
return -EINVAL;
|
|
}
|
|
|
|
kcop->ivlen = cop->iv ? ses_ptr->cdata.ivsize : 0;
|
|
if (kcop->ivlen > MAX_IV_SIZE) {
|
|
dbgp(2, "ivlen = %d is too large", kcop->ivlen);
|
|
return -EINVAL;
|
|
}
|
|
kcop->param_len = cop->param_len;
|
|
|
|
crypto_put_session(ses_ptr);
|
|
|
|
if (cop->iv) {
|
|
rc = copy_from_user(kcop->iv, cop->iv, kcop->ivlen);
|
|
if (unlikely(rc)) {
|
|
dbgp(2, "error copying IV (%d bytes), returned %d",
|
|
kcop->ivlen, rc);
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
if (cop->param) {
|
|
kcop->param = kzalloc(kcop->param_len, GFP_KERNEL);
|
|
if (!kcop->param) {
|
|
dbgp(2, "error allocating %d for param",
|
|
kcop->param_len);
|
|
return -ENOMEM;
|
|
}
|
|
rc = copy_from_user(kcop->param, cop->param, kcop->param_len);
|
|
if (unlikely(rc)) {
|
|
dbgp(2, "error copying param (%d bytes), returned %d",
|
|
kcop->param_len, rc);
|
|
return -EFAULT;
|
|
}
|
|
} else {
|
|
kcop->param = NULL;
|
|
kcop->param_len = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fill_cop_from_kcop(struct kernel_crypt_op *kcop, struct fcrypt *fcr)
|
|
{
|
|
int ret;
|
|
|
|
if (kcop->ivlen) {
|
|
ret = copy_to_user(kcop->cop.iv,
|
|
kcop->iv, kcop->ivlen);
|
|
if (unlikely(ret))
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int kcop_from_user(struct kernel_crypt_op *kcop,
|
|
struct fcrypt *fcr, void __user *arg)
|
|
{
|
|
if (unlikely(copy_from_user(&kcop->cop, arg, sizeof(kcop->cop))))
|
|
return -EFAULT;
|
|
|
|
if (kcop->cop.num_src_bufs > MAX_CRYPTO_BUFFERS ||
|
|
kcop->cop.num_dst_bufs > MAX_CRYPTO_BUFFERS) {
|
|
dbgp(2, "too many buffers, src: %d, dst: %d\n",
|
|
kcop->cop.num_src_bufs, kcop->cop.num_dst_bufs);
|
|
return -EINVAL;
|
|
}
|
|
return fill_kcop_from_cop(kcop, fcr);
|
|
}
|
|
|
|
static int kcop_to_user(struct kernel_crypt_op *kcop,
|
|
struct fcrypt *fcr, void __user *arg)
|
|
{
|
|
int ret;
|
|
|
|
ret = fill_cop_from_kcop(kcop, fcr);
|
|
kfree(kcop->param);
|
|
|
|
if (unlikely(ret)) {
|
|
dbgp(2, "Error in fill_cop_from_kcop");
|
|
return ret;
|
|
}
|
|
|
|
if (unlikely(copy_to_user(arg, &kcop->cop, sizeof(kcop->cop)))) {
|
|
dbgp(2, "Cannot copy to userspace");
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int hw_restriction_check(u32 nbufs, struct crypt_mem *buf,
|
|
struct crypto_session *ses_ptr)
|
|
{
|
|
int i = 0;
|
|
u32 length = 0, dma_length = 0;
|
|
int rc = 0;
|
|
|
|
for (i = 0; i < nbufs; i++) {
|
|
length = buf[i].length;
|
|
if (!(length % DMA_BLOCK_MODE_SIZE))
|
|
dma_length = length / DMA_BLOCK_MODE_SIZE;
|
|
else
|
|
dma_length = length;
|
|
if (dma_length >> 18) {
|
|
/* length too large for byte mode */
|
|
rc = -EINVAL;
|
|
dbgp(2, "size too large: %d\n", dma_length);
|
|
return rc;
|
|
}
|
|
|
|
if (length % ses_ptr->cdata.blocksize) {
|
|
if (ses_ptr->cdata.cipher == CRYPTO_OP_AES_CTR ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_SM4_CTR) {
|
|
if (i != (nbufs - 1)) {
|
|
dbgp(2, "unaligned buffer size: %d\n",
|
|
length);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
dbgp(2, "unaligned buffer size: %d\n",
|
|
length);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Increment 128-bit counter */
|
|
static void ctr_update_iv(unsigned char *val, unsigned int value)
|
|
{
|
|
int i;
|
|
|
|
while (value > 0) {
|
|
for (i = 15; i >= 0; i--) {
|
|
val[i] = (val[i] + 1) & 0xff;
|
|
if (val[i])
|
|
break;
|
|
}
|
|
value--;
|
|
}
|
|
}
|
|
|
|
int __crypto_run_physical(struct crypto_session *ses_ptr,
|
|
struct kernel_crypt_op *kcop)
|
|
{
|
|
int i = 0;
|
|
dma_addr_t dsc_addr = 0;
|
|
dma_addr_t dma_iv_addr = 0;
|
|
dma_addr_t cleanup_addr = 0;
|
|
struct dma_dsc *dsc = NULL;
|
|
u8 *iv = NULL;
|
|
u8 *key_clean = NULL;
|
|
struct device *dev = NULL;
|
|
int s = 0;
|
|
u32 nbufs = 0;
|
|
int rc = 0;
|
|
int block_mode = 0;
|
|
u32 src_bufsz = 0;
|
|
int err = 0;
|
|
const u32 DMA_BUF_SIZE = kcop->ivlen > 16 ?
|
|
DMA_KEY_IV_BUF_SIZE_64B : DMA_KEY_IV_BUF_SIZE;
|
|
u8 begin = 0;
|
|
int lock_ret = 0;
|
|
|
|
if (unlikely(!ses_ptr || !kcop)) {
|
|
dbgp(2, "no ses_ptr or no kcop\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev = crypto_dd->dev;
|
|
|
|
/* data sanity check for HW restrictions */
|
|
if (kcop->cop.num_src_bufs != kcop->cop.num_dst_bufs) {
|
|
dbgp(2, "incompatible number of buffers, src: %d, dst: %d\n",
|
|
kcop->cop.num_src_bufs, kcop->cop.num_dst_bufs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
nbufs = kcop->cop.num_src_bufs;
|
|
if (nbufs > MAX_CRYPTO_BUFFERS) {
|
|
dbgp(2, "too many buffers: %d\n", nbufs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < nbufs; i++) {
|
|
if (kcop->cop.src[i].length != kcop->cop.dst[i].length) {
|
|
dbgp(2, "incompatible buffer size\n");
|
|
dbgp(2, "buffer: %d, src: %d, dst: %d\n",
|
|
i, kcop->cop.src[i].length,
|
|
kcop->cop.src[i].length);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
if (hw_restriction_check(nbufs, kcop->cop.src, ses_ptr)) {
|
|
dbgp(2, "checked fail in src\n");
|
|
return -EINVAL;
|
|
}
|
|
if (hw_restriction_check(nbufs, kcop->cop.dst, ses_ptr)) {
|
|
dbgp(2, "checked fail in dst\n");
|
|
return -EINVAL;
|
|
}
|
|
for (i = 0; i < nbufs; i++)
|
|
src_bufsz += kcop->cop.src[i].length;
|
|
|
|
/* extra three descriptor for key and IV and cleanup */
|
|
dsc = dma_alloc_coherent(dev, (nbufs + 3) * sizeof(struct dma_dsc),
|
|
&dsc_addr, GFP_KERNEL | GFP_DMA);
|
|
if (!dsc) {
|
|
dbgp(2, "cannot allocate dsc\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
dsc[0].src_addr = (u32)(0xffffff00 | ses_ptr->cdata.kte);
|
|
dsc[0].tgt_addr = 0;
|
|
dsc[0].dsc_cfg.d32 = 0;
|
|
/* HW bug, key length needs to stay at 16 */
|
|
dsc[0].dsc_cfg.b.length = ses_ptr->cdata.keylen <= 16 ?
|
|
16 : ses_ptr->cdata.keylen;
|
|
dsc[0].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[0].dsc_cfg.b.eoc = 0;
|
|
dsc[0].dsc_cfg.b.owner = 1;
|
|
s++;
|
|
|
|
if (kcop->ivlen) {
|
|
iv = dma_alloc_coherent(dev, kcop->ivlen,
|
|
&dma_iv_addr, GFP_KERNEL | GFP_DMA);
|
|
if (!iv) {
|
|
dbgp(2, "cannot allocate iv\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memcpy(iv, kcop->iv, kcop->ivlen);
|
|
dsc[1].src_addr = (u32)dma_iv_addr;
|
|
dsc[1].tgt_addr = 32;
|
|
dsc[1].dsc_cfg.d32 = 0;
|
|
/* HW bug, iv length needs to stay at 16 */
|
|
dsc[1].dsc_cfg.b.length = kcop->ivlen <= 16 ? 16 : kcop->ivlen;
|
|
dsc[1].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[1].dsc_cfg.b.eoc = 0;
|
|
dsc[1].dsc_cfg.b.owner = 1;
|
|
s++;
|
|
}
|
|
|
|
/* Do nothing for IV: We do not know IV in encrypt */
|
|
|
|
if (ses_ptr->cdata.cipher == CRYPTO_OP_S17_ECB ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CBC ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CTR)
|
|
begin = 1;
|
|
else
|
|
begin = 0;
|
|
|
|
for (i = 0; i < nbufs; i++) {
|
|
u32 length = kcop->cop.src[i].length;
|
|
|
|
if (!(length % DMA_BLOCK_MODE_SIZE))
|
|
block_mode = 1;
|
|
else
|
|
block_mode = 0;
|
|
|
|
dsc[s + i].src_addr = (uintptr_t)kcop->cop.src[i].addr;
|
|
dsc[s + i].tgt_addr = (uintptr_t)kcop->cop.dst[i].addr;
|
|
dsc[s + i].dsc_cfg.d32 = 0;
|
|
dsc[s + i].dsc_cfg.b.enc_sha_only = (kcop->cop.op ==
|
|
CRYPTO_OP_ENCRYPT);
|
|
dsc[s + i].dsc_cfg.b.block = block_mode;
|
|
if (block_mode)
|
|
dsc[s + i].dsc_cfg.b.length =
|
|
length / DMA_BLOCK_MODE_SIZE;
|
|
else
|
|
dsc[s + i].dsc_cfg.b.length = length;
|
|
dsc[s + i].dsc_cfg.b.op_mode = ses_ptr->cdata.op_mode;
|
|
dsc[s + i].dsc_cfg.b.mode = ses_ptr->cdata.crypt_mode;
|
|
dsc[s + i].dsc_cfg.b.begin = begin;
|
|
dsc[s + i].dsc_cfg.b.eoc = 0;
|
|
dsc[s + i].dsc_cfg.b.owner = 1;
|
|
|
|
begin = 0;
|
|
}
|
|
key_clean = dma_alloc_coherent(dev, DMA_BUF_SIZE, &cleanup_addr,
|
|
GFP_KERNEL | GFP_DMA);
|
|
if (!key_clean) {
|
|
dbgp(2, "cannot allocate key_clean\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memset(key_clean, 0, DMA_BUF_SIZE);
|
|
dsc[s + i].src_addr = cleanup_addr;
|
|
dsc[s + i].tgt_addr = 0;
|
|
dsc[s + i].dsc_cfg.d32 = 0;
|
|
dsc[s + i].dsc_cfg.b.length = DMA_BUF_SIZE;
|
|
dsc[s + i].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[s + i].dsc_cfg.b.eoc = 1;
|
|
dsc[s + i].dsc_cfg.b.owner = 1;
|
|
|
|
aml_dma_debug(dsc, s + i + 1, __func__,
|
|
crypto_dd->thread, crypto_dd->status);
|
|
|
|
lock_ret = mutex_lock_interruptible(&crypto_dd->lock);
|
|
if (lock_ret)
|
|
goto error;
|
|
crypto_dd->dma_busy = 1;
|
|
crypto_dd->processing = current;
|
|
#if !USE_BUSY_POLLING
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
#endif
|
|
|
|
aml_write_crypto_reg(crypto_dd->thread, (uintptr_t)dsc_addr | 2);
|
|
|
|
#if USE_BUSY_POLLING
|
|
while ((err = aml_read_crypto_reg(crypto_dd->status)) == 0)
|
|
;
|
|
aml_write_crypto_reg(crypto_dd->status, 0xf);
|
|
#else
|
|
schedule();
|
|
err = crypto_dd->err;
|
|
#endif
|
|
crypto_dd->dma_busy = 0;
|
|
mutex_unlock(&crypto_dd->lock);
|
|
|
|
if (err & DMA_STATUS_KEY_ERROR) {
|
|
rc = -EACCES;
|
|
goto error;
|
|
}
|
|
|
|
if (ses_ptr->cdata.op_mode == OP_MODE_CBC) {
|
|
/* Do nothing for IV: We do not know IV in decrypt */
|
|
} else if (ses_ptr->cdata.op_mode == OP_MODE_CTR) {
|
|
u32 dma_nblock =
|
|
(src_bufsz + ses_ptr->cdata.blocksize - 1)
|
|
/ ses_ptr->cdata.blocksize;
|
|
ctr_update_iv(kcop->iv, dma_nblock);
|
|
}
|
|
|
|
error:
|
|
if (iv)
|
|
dma_free_coherent(dev, kcop->ivlen, iv, dma_iv_addr);
|
|
if (dsc)
|
|
dma_free_coherent(dev, (nbufs + 3) * sizeof(struct dma_dsc),
|
|
dsc, dsc_addr);
|
|
if (key_clean)
|
|
dma_free_coherent(dev, DMA_BUF_SIZE,
|
|
key_clean, cleanup_addr);
|
|
return rc;
|
|
}
|
|
|
|
static int __copy_buffers_in(struct crypt_mem **src, u32 total, u32 *offset,
|
|
u8 *buf, int buflen)
|
|
{
|
|
int rc = 0;
|
|
int count, lvl = 0;
|
|
|
|
while (total && buflen) {
|
|
count = min((*src)->length - *offset, total);
|
|
count = min(count, buflen);
|
|
|
|
if (!count)
|
|
return lvl;
|
|
|
|
rc = copy_from_user(buf + lvl, (*src)->addr + *offset, count);
|
|
if (unlikely(rc)) {
|
|
dbgp(2, "error copying src (%d bytes)\n", count);
|
|
dbgp(2, "copy_from_user returned %d", rc);
|
|
return -1;
|
|
}
|
|
|
|
lvl += count;
|
|
buflen -= count;
|
|
*offset += count;
|
|
total -= count;
|
|
|
|
if (*offset == (*src)->length) {
|
|
(*src)++;
|
|
*offset = 0;
|
|
}
|
|
}
|
|
|
|
return lvl;
|
|
}
|
|
|
|
static int __copy_buffers_out(struct crypt_mem **dst, u32 total,
|
|
u32 *offset, u8 *buf)
|
|
{
|
|
int rc = 0;
|
|
int count, lvl = 0;
|
|
|
|
while (total) {
|
|
count = min((*dst)->length - *offset, total);
|
|
|
|
if (!count)
|
|
return lvl;
|
|
|
|
rc = copy_to_user((*dst)->addr + *offset, buf + lvl, count);
|
|
if (unlikely(rc)) {
|
|
dbgp(2, "error copying src (%d bytes)\n", count);
|
|
dbgp(2, "copy_from_user returned %d\n", rc);
|
|
return -1;
|
|
}
|
|
|
|
lvl += count;
|
|
*offset += count;
|
|
total -= count;
|
|
|
|
if (*offset == (*dst)->length) {
|
|
(*dst)++;
|
|
*offset = 0;
|
|
}
|
|
}
|
|
|
|
return lvl;
|
|
}
|
|
|
|
int __crypto_run_virt_to_phys(struct crypto_session *ses_ptr,
|
|
struct kernel_crypt_op *kcop)
|
|
{
|
|
int i = 0;
|
|
dma_addr_t dsc_addr = 0;
|
|
dma_addr_t dma_iv_addr = 0;
|
|
dma_addr_t dma_buf = 0;
|
|
dma_addr_t cleanup_addr = 0;
|
|
struct dma_dsc *dsc = NULL;
|
|
u8 *iv = NULL;
|
|
u8 *key_clean = NULL;
|
|
struct device *dev = NULL;
|
|
int s = 0;
|
|
u32 nbufs = 0;
|
|
int rc = 0;
|
|
int block_mode = 0;
|
|
u8 *tmp_buf = NULL, *tmp_buf2 = NULL;
|
|
struct crypt_mem *src = kcop->cop.src;
|
|
u32 length = 0;
|
|
u32 offset = 0;
|
|
int count = 0;
|
|
u32 src_bufsz = 0;
|
|
int err = 0;
|
|
const u32 DMA_BUF_SIZE = kcop->ivlen > 16 ?
|
|
DMA_KEY_IV_BUF_SIZE_64B : DMA_KEY_IV_BUF_SIZE;
|
|
u8 begin = 0;
|
|
int lock_ret = 0;
|
|
|
|
if (unlikely(!ses_ptr || !kcop)) {
|
|
dbgp(2, "no ses_ptr or no kcop\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev = crypto_dd->dev;
|
|
|
|
/* data sanity check for HW restrictions */
|
|
nbufs = kcop->cop.num_dst_bufs;
|
|
if (nbufs > MAX_CRYPTO_BUFFERS) {
|
|
dbgp(2, "too many buffers: %d\n", nbufs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < nbufs; i++)
|
|
src_bufsz += kcop->cop.src[i].length;
|
|
if (hw_restriction_check(nbufs, kcop->cop.dst, ses_ptr)) {
|
|
dbgp(2, "checked fail in dst\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* three descriptor for key, IV and data */
|
|
dsc = dma_alloc_coherent(dev, 3 * sizeof(struct dma_dsc),
|
|
&dsc_addr, GFP_KERNEL | GFP_DMA);
|
|
if (!dsc) {
|
|
dbgp(2, "cannot allocate dsc\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
dsc[0].src_addr = (u32)(0xffffff00 | ses_ptr->cdata.kte);
|
|
dsc[0].tgt_addr = 0;
|
|
dsc[0].dsc_cfg.d32 = 0;
|
|
/* HW bug, key length needs to stay at 16 */
|
|
dsc[0].dsc_cfg.b.length = ses_ptr->cdata.keylen <= 16 ?
|
|
16 : ses_ptr->cdata.keylen;
|
|
dsc[0].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[0].dsc_cfg.b.eoc = 0;
|
|
dsc[0].dsc_cfg.b.owner = 1;
|
|
s++;
|
|
|
|
if (kcop->ivlen) {
|
|
iv = dma_alloc_coherent(dev, kcop->ivlen,
|
|
&dma_iv_addr, GFP_KERNEL | GFP_DMA);
|
|
if (!iv) {
|
|
dbgp(2, "cannot allocate iv\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memcpy(iv, kcop->iv, kcop->ivlen);
|
|
dsc[1].src_addr = (u32)dma_iv_addr;
|
|
dsc[1].tgt_addr = 32;
|
|
dsc[1].dsc_cfg.d32 = 0;
|
|
/* HW bug, iv length needs to stay at 16 */
|
|
dsc[1].dsc_cfg.b.length = kcop->ivlen <= 16 ? 16 : kcop->ivlen;
|
|
dsc[1].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[1].dsc_cfg.b.eoc = 0;
|
|
dsc[1].dsc_cfg.b.owner = 1;
|
|
s++;
|
|
}
|
|
|
|
if (ses_ptr->cdata.op_mode == OP_MODE_CBC &&
|
|
kcop->cop.op != CRYPTO_OP_ENCRYPT) {
|
|
uintptr_t s_end = (uintptr_t)kcop->cop.src[nbufs - 1].addr +
|
|
kcop->cop.src[nbufs - 1].length;
|
|
rc = copy_from_user(kcop->iv,
|
|
(void *)(s_end - ses_ptr->cdata.blocksize),
|
|
ses_ptr->cdata.blocksize);
|
|
if (unlikely(rc)) {
|
|
dbgp(2, "failed to get next IV\n");
|
|
rc = -EPERM;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
if (ses_ptr->cdata.cipher == CRYPTO_OP_S17_ECB ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CBC ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CTR)
|
|
begin = 1;
|
|
else
|
|
begin = 0;
|
|
|
|
for (i = 0; i < nbufs; i++) {
|
|
length = kcop->cop.dst[i].length;
|
|
tmp_buf2 = krealloc(tmp_buf, length, GFP_KERNEL | GFP_DMA);
|
|
if (!tmp_buf2) {
|
|
dbgp(2, "cannot allocate memory, size: %d\n", length);
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
tmp_buf = tmp_buf2;
|
|
count = __copy_buffers_in(&src, length, &offset,
|
|
tmp_buf, length);
|
|
if (unlikely(count != length)) {
|
|
dbgp(2, "incompatible num %d %d read\n", count, length);
|
|
goto error;
|
|
}
|
|
|
|
if (!(length % DMA_BLOCK_MODE_SIZE))
|
|
block_mode = 1;
|
|
else
|
|
block_mode = 0;
|
|
|
|
dma_buf = dma_map_single(dev, tmp_buf, length, DMA_TO_DEVICE);
|
|
|
|
dsc[s].src_addr = dma_buf;
|
|
dsc[s].tgt_addr = (uintptr_t)kcop->cop.dst[i].addr;
|
|
dsc[s].dsc_cfg.d32 = 0;
|
|
dsc[s].dsc_cfg.b.enc_sha_only = (kcop->cop.op
|
|
== CRYPTO_OP_ENCRYPT);
|
|
dsc[s].dsc_cfg.b.block = block_mode;
|
|
if (block_mode)
|
|
dsc[s].dsc_cfg.b.length =
|
|
length / DMA_BLOCK_MODE_SIZE;
|
|
else
|
|
dsc[s].dsc_cfg.b.length = length;
|
|
dsc[s].dsc_cfg.b.op_mode = ses_ptr->cdata.op_mode;
|
|
dsc[s].dsc_cfg.b.mode = ses_ptr->cdata.crypt_mode;
|
|
dsc[s].dsc_cfg.b.begin = begin;
|
|
dsc[s].dsc_cfg.b.eoc = 1;
|
|
dsc[s].dsc_cfg.b.owner = 1;
|
|
|
|
begin = 0;
|
|
|
|
lock_ret = mutex_lock_interruptible(&crypto_dd->lock);
|
|
if (lock_ret)
|
|
goto error;
|
|
crypto_dd->dma_busy = 1;
|
|
crypto_dd->processing = current;
|
|
#if !USE_BUSY_POLLING
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
#endif
|
|
|
|
aml_dma_debug(dsc, s + 1, __func__,
|
|
crypto_dd->thread, crypto_dd->status);
|
|
aml_write_crypto_reg(crypto_dd->thread,
|
|
(uintptr_t)dsc_addr | 2);
|
|
|
|
#if USE_BUSY_POLLING
|
|
while ((err = aml_read_crypto_reg(crypto_dd->status)) == 0)
|
|
;
|
|
aml_write_crypto_reg(crypto_dd->status, 0xf);
|
|
#else
|
|
schedule();
|
|
err = crypto_dd->err;
|
|
#endif
|
|
crypto_dd->dma_busy = 0;
|
|
mutex_unlock(&crypto_dd->lock);
|
|
dma_unmap_single(dev, dma_buf, length, DMA_TO_DEVICE);
|
|
if (err & DMA_STATUS_KEY_ERROR) {
|
|
rc = -EACCES;
|
|
goto error;
|
|
}
|
|
s = 0;
|
|
}
|
|
|
|
key_clean = dma_alloc_coherent(dev, DMA_BUF_SIZE, &cleanup_addr,
|
|
GFP_KERNEL | GFP_DMA);
|
|
if (!key_clean) {
|
|
dbgp(2, "cannot allocate key_clean\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memset(key_clean, 0, DMA_BUF_SIZE);
|
|
dsc[0].src_addr = cleanup_addr;
|
|
dsc[0].tgt_addr = 0;
|
|
dsc[0].dsc_cfg.d32 = 0;
|
|
dsc[0].dsc_cfg.b.length = DMA_BUF_SIZE;
|
|
dsc[0].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[0].dsc_cfg.b.eoc = 1;
|
|
dsc[0].dsc_cfg.b.owner = 1;
|
|
|
|
lock_ret = mutex_lock_interruptible(&crypto_dd->lock);
|
|
if (lock_ret)
|
|
goto error;
|
|
crypto_dd->dma_busy = 1;
|
|
crypto_dd->processing = current;
|
|
aml_dma_debug(dsc, 1, __func__, crypto_dd->thread, crypto_dd->status);
|
|
aml_write_crypto_reg(crypto_dd->thread, (uintptr_t)dsc_addr | 2);
|
|
#if USE_BUSY_POLLING
|
|
while ((err = aml_read_crypto_reg(crypto_dd->status)) == 0)
|
|
;
|
|
aml_write_crypto_reg(crypto_dd->status, 0xf);
|
|
#else
|
|
schedule();
|
|
err = crypto_dd->err;
|
|
#endif
|
|
crypto_dd->dma_busy = 0;
|
|
mutex_unlock(&crypto_dd->lock);
|
|
if (err & DMA_STATUS_KEY_ERROR) {
|
|
rc = -EACCES;
|
|
goto error;
|
|
}
|
|
|
|
if (ses_ptr->cdata.op_mode == OP_MODE_CBC) {
|
|
/* Do nothing for IV: We do not know IV in decrypt */
|
|
} else if (ses_ptr->cdata.op_mode == OP_MODE_CTR) {
|
|
u32 dma_nblock =
|
|
(src_bufsz + ses_ptr->cdata.blocksize - 1)
|
|
/ ses_ptr->cdata.blocksize;
|
|
ctr_update_iv(kcop->iv, dma_nblock);
|
|
}
|
|
|
|
error:
|
|
if (iv)
|
|
dma_free_coherent(dev, kcop->ivlen, iv, dma_iv_addr);
|
|
if (dsc)
|
|
dma_free_coherent(dev, 3 * sizeof(struct dma_dsc),
|
|
dsc, dsc_addr);
|
|
if (key_clean)
|
|
dma_free_coherent(dev, DMA_BUF_SIZE,
|
|
key_clean, cleanup_addr);
|
|
kfree(tmp_buf);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int __crypto_run_phys_to_virt(struct crypto_session *ses_ptr,
|
|
struct kernel_crypt_op *kcop)
|
|
{
|
|
int i = 0;
|
|
dma_addr_t dsc_addr = 0;
|
|
dma_addr_t dma_iv_addr = 0;
|
|
dma_addr_t dma_buf = 0;
|
|
dma_addr_t cleanup_addr = 0;
|
|
struct dma_dsc *dsc = NULL;
|
|
u8 *iv = NULL;
|
|
u8 *key_clean = NULL;
|
|
struct device *dev = NULL;
|
|
int s = 0;
|
|
u32 nbufs = 0;
|
|
int rc = 0;
|
|
int block_mode = 0;
|
|
u8 *tmp_buf = NULL, *tmp_buf2 = NULL;
|
|
u32 length = 0;
|
|
u32 offset_dst = 0;
|
|
struct crypt_mem *dst = kcop->cop.dst;
|
|
int count_dst = 0;
|
|
u32 src_bufsz = 0;
|
|
int err = 0;
|
|
const u32 DMA_BUF_SIZE = kcop->ivlen > 16 ?
|
|
DMA_KEY_IV_BUF_SIZE_64B : DMA_KEY_IV_BUF_SIZE;
|
|
u8 begin = 0;
|
|
int lock_ret = 0;
|
|
|
|
if (unlikely(!ses_ptr || !kcop)) {
|
|
dbgp(2, "no ses_ptr or no kcop\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev = crypto_dd->dev;
|
|
|
|
/* data sanity check for HW restrictions */
|
|
nbufs = kcop->cop.num_src_bufs;
|
|
if (nbufs > MAX_CRYPTO_BUFFERS) {
|
|
dbgp(2, "too many buffers: %d\n", nbufs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (hw_restriction_check(nbufs, kcop->cop.src, ses_ptr)) {
|
|
dbgp(2, "checked fail in dst\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < nbufs; i++)
|
|
src_bufsz += kcop->cop.src[i].length;
|
|
|
|
/* three descriptor for key, IV and data */
|
|
dsc = dma_alloc_coherent(dev, 3 * sizeof(struct dma_dsc),
|
|
&dsc_addr, GFP_KERNEL | GFP_DMA);
|
|
if (!dsc) {
|
|
dbgp(2, "cannot allocate dsc\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
dsc[0].src_addr = (u32)(0xffffff00 | ses_ptr->cdata.kte);
|
|
dsc[0].tgt_addr = 0;
|
|
dsc[0].dsc_cfg.d32 = 0;
|
|
/* HW bug, key length needs to stay at 16 */
|
|
dsc[0].dsc_cfg.b.length = ses_ptr->cdata.keylen <= 16 ?
|
|
16 : ses_ptr->cdata.keylen;
|
|
dsc[0].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[0].dsc_cfg.b.eoc = 0;
|
|
dsc[0].dsc_cfg.b.owner = 1;
|
|
s++;
|
|
|
|
if (kcop->ivlen) {
|
|
iv = dma_alloc_coherent(dev, kcop->ivlen,
|
|
&dma_iv_addr, GFP_KERNEL | GFP_DMA);
|
|
if (!iv) {
|
|
dbgp(2, "cannot allocate iv\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memcpy(iv, kcop->iv, kcop->ivlen);
|
|
dsc[1].src_addr = (u32)dma_iv_addr;
|
|
dsc[1].tgt_addr = 32;
|
|
dsc[1].dsc_cfg.d32 = 0;
|
|
/* HW bug, iv length needs to stay at 16 */
|
|
dsc[1].dsc_cfg.b.length = kcop->ivlen <= 16 ? 16 : kcop->ivlen;
|
|
dsc[1].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[1].dsc_cfg.b.eoc = 0;
|
|
dsc[1].dsc_cfg.b.owner = 1;
|
|
s++;
|
|
}
|
|
|
|
/* Do nothing for IV: We do not know IV in encrypt */
|
|
|
|
|
|
if (ses_ptr->cdata.cipher == CRYPTO_OP_S17_ECB ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CBC ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CTR)
|
|
begin = 1;
|
|
else
|
|
begin = 0;
|
|
|
|
for (i = 0; i < nbufs; i++) {
|
|
length = kcop->cop.src[i].length;
|
|
tmp_buf2 = krealloc(tmp_buf, length, GFP_KERNEL | GFP_DMA);
|
|
if (!tmp_buf2) {
|
|
dbgp(2, "cannot allocate memory, size: %d\n", length);
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
tmp_buf = tmp_buf2;
|
|
|
|
if (!(length % DMA_BLOCK_MODE_SIZE))
|
|
block_mode = 1;
|
|
else
|
|
block_mode = 0;
|
|
|
|
dma_buf = dma_map_single(dev, tmp_buf, length, DMA_FROM_DEVICE);
|
|
|
|
dsc[s].src_addr = (uintptr_t)kcop->cop.src[i].addr;
|
|
dsc[s].tgt_addr = dma_buf;
|
|
dsc[s].dsc_cfg.d32 = 0;
|
|
dsc[s].dsc_cfg.b.enc_sha_only = (kcop->cop.op
|
|
== CRYPTO_OP_ENCRYPT);
|
|
dsc[s].dsc_cfg.b.block = block_mode;
|
|
if (block_mode)
|
|
dsc[s].dsc_cfg.b.length =
|
|
length / DMA_BLOCK_MODE_SIZE;
|
|
else
|
|
dsc[s].dsc_cfg.b.length = length;
|
|
dsc[s].dsc_cfg.b.op_mode = ses_ptr->cdata.op_mode;
|
|
dsc[s].dsc_cfg.b.mode = ses_ptr->cdata.crypt_mode;
|
|
dsc[s].dsc_cfg.b.begin = begin;
|
|
dsc[s].dsc_cfg.b.eoc = 1;
|
|
dsc[s].dsc_cfg.b.owner = 1;
|
|
|
|
begin = 0;
|
|
|
|
lock_ret = mutex_lock_interruptible(&crypto_dd->lock);
|
|
if (lock_ret)
|
|
goto error;
|
|
crypto_dd->dma_busy = 1;
|
|
crypto_dd->processing = current;
|
|
#if !USE_BUSY_POLLING
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
#endif
|
|
|
|
aml_dma_debug(dsc, s + 1, __func__,
|
|
crypto_dd->thread, crypto_dd->status);
|
|
aml_write_crypto_reg(crypto_dd->thread,
|
|
(uintptr_t)dsc_addr | 2);
|
|
|
|
#if USE_BUSY_POLLING
|
|
while ((err = aml_read_crypto_reg(crypto_dd->status)) == 0)
|
|
;
|
|
aml_write_crypto_reg(crypto_dd->status, 0xf);
|
|
#else
|
|
schedule();
|
|
err = crypto_dd->err;
|
|
#endif
|
|
crypto_dd->dma_busy = 0;
|
|
mutex_unlock(&crypto_dd->lock);
|
|
dma_unmap_single(dev, dma_buf, length, DMA_FROM_DEVICE);
|
|
if (err & DMA_STATUS_KEY_ERROR) {
|
|
rc = -EACCES;
|
|
goto error;
|
|
}
|
|
count_dst = __copy_buffers_out(&dst, length,
|
|
&offset_dst, tmp_buf);
|
|
if (unlikely(count_dst != length)) {
|
|
dbgp(2, "incorrect number of data, c = %d, dst = %d\n",
|
|
length, count_dst);
|
|
goto error;
|
|
}
|
|
s = 0;
|
|
}
|
|
|
|
key_clean = dma_alloc_coherent(dev, DMA_BUF_SIZE, &cleanup_addr,
|
|
GFP_KERNEL | GFP_DMA);
|
|
if (!key_clean) {
|
|
dbgp(2, "cannot allocate key_clean\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memset(key_clean, 0, DMA_BUF_SIZE);
|
|
dsc[0].src_addr = cleanup_addr;
|
|
dsc[0].tgt_addr = 0;
|
|
dsc[0].dsc_cfg.d32 = 0;
|
|
dsc[0].dsc_cfg.b.length = DMA_BUF_SIZE;
|
|
dsc[0].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[0].dsc_cfg.b.eoc = 1;
|
|
dsc[0].dsc_cfg.b.owner = 1;
|
|
|
|
lock_ret = mutex_lock_interruptible(&crypto_dd->lock);
|
|
if (lock_ret)
|
|
goto error;
|
|
crypto_dd->dma_busy = 1;
|
|
crypto_dd->processing = current;
|
|
aml_dma_debug(dsc, 1, __func__, crypto_dd->thread, crypto_dd->status);
|
|
aml_write_crypto_reg(crypto_dd->thread, (uintptr_t)dsc_addr | 2);
|
|
#if USE_BUSY_POLLING
|
|
while ((err = aml_read_crypto_reg(crypto_dd->status)) == 0)
|
|
;
|
|
aml_write_crypto_reg(crypto_dd->status, 0xf);
|
|
#else
|
|
schedule();
|
|
err = crypto_dd->err;
|
|
#endif
|
|
crypto_dd->dma_busy = 0;
|
|
mutex_unlock(&crypto_dd->lock);
|
|
if (err & DMA_STATUS_KEY_ERROR) {
|
|
rc = -EACCES;
|
|
goto error;
|
|
}
|
|
|
|
if (ses_ptr->cdata.op_mode == OP_MODE_CBC) {
|
|
if (kcop->cop.op == CRYPTO_OP_ENCRYPT) {
|
|
memcpy(kcop->iv, tmp_buf + count_dst -
|
|
ses_ptr->cdata.blocksize,
|
|
ses_ptr->cdata.blocksize);
|
|
}
|
|
} else if (ses_ptr->cdata.op_mode == OP_MODE_CTR) {
|
|
u32 dma_nblock =
|
|
(src_bufsz + ses_ptr->cdata.blocksize - 1)
|
|
/ ses_ptr->cdata.blocksize;
|
|
ctr_update_iv(kcop->iv, dma_nblock);
|
|
}
|
|
|
|
error:
|
|
if (iv)
|
|
dma_free_coherent(dev, kcop->ivlen, iv, dma_iv_addr);
|
|
if (dsc)
|
|
dma_free_coherent(dev, 3 * sizeof(struct dma_dsc),
|
|
dsc, dsc_addr);
|
|
if (key_clean)
|
|
dma_free_coherent(dev, DMA_BUF_SIZE,
|
|
key_clean, cleanup_addr);
|
|
kfree(tmp_buf);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int __crypto_run_virtual(struct crypto_session *ses_ptr,
|
|
struct kernel_crypt_op *kcop)
|
|
{
|
|
int i = 0;
|
|
dma_addr_t dsc_addr = 0;
|
|
dma_addr_t dma_iv_addr = 0;
|
|
dma_addr_t dma_buf = 0;
|
|
dma_addr_t cleanup_addr = 0;
|
|
struct dma_dsc *dsc = NULL;
|
|
u8 *key_clean = NULL;
|
|
u8 *iv = NULL;
|
|
struct device *dev = NULL;
|
|
int s = 0;
|
|
u32 nsrc_bufs = 0;
|
|
u32 ndst_bufs = 0;
|
|
u32 total = 0;
|
|
u32 src_bufsz = 0;
|
|
u32 dst_bufsz = 0;
|
|
u8 *tmp_buf = NULL;
|
|
int rc = 0;
|
|
u32 offset = 0, offset_dst = 0;
|
|
struct crypt_mem *src = kcop->cop.src;
|
|
struct crypt_mem *dst = kcop->cop.dst;
|
|
int count = 0, count_dst = 0;
|
|
int err = 0;
|
|
const u32 DMA_BUF_SIZE = kcop->ivlen > 16 ?
|
|
DMA_KEY_IV_BUF_SIZE_64B : DMA_KEY_IV_BUF_SIZE;
|
|
u8 begin = 0;
|
|
int lock_ret = 0;
|
|
|
|
if (unlikely(!ses_ptr || !kcop)) {
|
|
dbgp(2, "no ses_ptr or no kcop\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev = crypto_dd->dev;
|
|
|
|
nsrc_bufs = kcop->cop.num_src_bufs;
|
|
if (nsrc_bufs > MAX_CRYPTO_BUFFERS) {
|
|
dbgp(2, "too many buffers: %d\n", nsrc_bufs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < nsrc_bufs; i++)
|
|
src_bufsz += kcop->cop.src[i].length;
|
|
total = src_bufsz;
|
|
|
|
ndst_bufs = kcop->cop.num_dst_bufs;
|
|
if (ndst_bufs > MAX_CRYPTO_BUFFERS) {
|
|
dbgp(2, "too many buffers: %d\n", ndst_bufs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < ndst_bufs; i++)
|
|
dst_bufsz += kcop->cop.dst[i].length;
|
|
|
|
if (dst_bufsz < src_bufsz) {
|
|
dbgp(2, "buffer size mismatch, src: %d, dst: %d\n",
|
|
src_bufsz, dst_bufsz);
|
|
return -EINVAL;
|
|
}
|
|
|
|
tmp_buf = (void *)__get_free_pages(GFP_KERNEL, 0);
|
|
if (!tmp_buf) {
|
|
dbgp(2, "cannot allocate tmpbuf\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
dma_buf = dma_map_single(dev, tmp_buf, PAGE_SIZE, DMA_TO_DEVICE);
|
|
|
|
/* three descriptor for key, IV and data */
|
|
dsc = dma_alloc_coherent(dev, 3 * sizeof(struct dma_dsc),
|
|
&dsc_addr, GFP_KERNEL | GFP_DMA);
|
|
if (!dsc) {
|
|
dbgp(2, "cannot allocate dsc\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
dsc[0].src_addr = (u32)(0xffffff00 | ses_ptr->cdata.kte);
|
|
dsc[0].tgt_addr = 0;
|
|
dsc[0].dsc_cfg.d32 = 0;
|
|
/* HW bug, key length needs to stay at 16 */
|
|
dsc[0].dsc_cfg.b.length = ses_ptr->cdata.keylen <= 16 ?
|
|
16 : ses_ptr->cdata.keylen;
|
|
dsc[0].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[0].dsc_cfg.b.eoc = 0;
|
|
dsc[0].dsc_cfg.b.owner = 1;
|
|
s++;
|
|
|
|
if (kcop->ivlen) {
|
|
iv = dma_alloc_coherent(dev, kcop->ivlen,
|
|
&dma_iv_addr, GFP_KERNEL | GFP_DMA);
|
|
if (!iv) {
|
|
dbgp(2, "cannot allocate iv\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memcpy(iv, kcop->iv, kcop->ivlen);
|
|
|
|
dsc[1].src_addr = (u32)dma_iv_addr;
|
|
dsc[1].tgt_addr = 32;
|
|
dsc[1].dsc_cfg.d32 = 0;
|
|
/* HW bug, iv length needs to stay at 16 */
|
|
dsc[1].dsc_cfg.b.length = kcop->ivlen <= 16 ? 16 : kcop->ivlen;
|
|
dsc[1].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[1].dsc_cfg.b.eoc = 0;
|
|
dsc[1].dsc_cfg.b.owner = 1;
|
|
s++;
|
|
}
|
|
|
|
if (ses_ptr->cdata.op_mode == OP_MODE_CBC &&
|
|
kcop->cop.op != CRYPTO_OP_ENCRYPT) {
|
|
uintptr_t s_end = (uintptr_t)kcop->cop.src[nsrc_bufs - 1].addr +
|
|
kcop->cop.src[nsrc_bufs - 1].length;
|
|
rc = copy_from_user(kcop->iv,
|
|
(void *)(s_end - ses_ptr->cdata.blocksize),
|
|
ses_ptr->cdata.blocksize);
|
|
if (unlikely(rc)) {
|
|
dbgp(2, "failed to get next IV\n");
|
|
rc = -EPERM;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
if (ses_ptr->cdata.cipher == CRYPTO_OP_S17_ECB ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CBC ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CTR)
|
|
begin = 1;
|
|
else
|
|
begin = 0;
|
|
|
|
while (total) {
|
|
count = __copy_buffers_in(&src, total, &offset,
|
|
tmp_buf, PAGE_SIZE);
|
|
if (count < 0)
|
|
goto error;
|
|
dma_sync_single_for_device(dev, dma_buf,
|
|
PAGE_SIZE, DMA_TO_DEVICE);
|
|
|
|
dsc[s].src_addr = dma_buf;
|
|
dsc[s].tgt_addr = dma_buf;
|
|
dsc[s].dsc_cfg.d32 = 0;
|
|
dsc[s].dsc_cfg.b.enc_sha_only = (kcop->cop.op
|
|
== CRYPTO_OP_ENCRYPT);
|
|
dsc[s].dsc_cfg.b.length = count;
|
|
dsc[s].dsc_cfg.b.op_mode = ses_ptr->cdata.op_mode;
|
|
dsc[s].dsc_cfg.b.mode = ses_ptr->cdata.crypt_mode;
|
|
dsc[s].dsc_cfg.b.begin = begin;
|
|
dsc[s].dsc_cfg.b.eoc = 1;
|
|
dsc[s].dsc_cfg.b.owner = 1;
|
|
|
|
begin = 0;
|
|
|
|
lock_ret = mutex_lock_interruptible(&crypto_dd->lock);
|
|
if (lock_ret)
|
|
goto error;
|
|
crypto_dd->dma_busy = 1;
|
|
crypto_dd->processing = current;
|
|
#if !USE_BUSY_POLLING
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
#endif
|
|
|
|
aml_dma_debug(dsc, s + 1, __func__,
|
|
crypto_dd->thread, crypto_dd->status);
|
|
aml_write_crypto_reg(crypto_dd->thread,
|
|
(uintptr_t)dsc_addr | 2);
|
|
|
|
#if USE_BUSY_POLLING
|
|
while ((err = aml_read_crypto_reg(crypto_dd->status)) == 0)
|
|
;
|
|
aml_write_crypto_reg(crypto_dd->status, 0xf);
|
|
#else
|
|
schedule();
|
|
err = crypto_dd->err;
|
|
#endif
|
|
crypto_dd->dma_busy = 0;
|
|
mutex_unlock(&crypto_dd->lock);
|
|
dma_sync_single_for_cpu(dev, dma_buf,
|
|
PAGE_SIZE, DMA_FROM_DEVICE);
|
|
if (err & DMA_STATUS_KEY_ERROR) {
|
|
rc = -EACCES;
|
|
goto error;
|
|
}
|
|
count_dst = __copy_buffers_out(&dst, count,
|
|
&offset_dst, tmp_buf);
|
|
if (unlikely(count_dst != count)) {
|
|
dbgp(2, "incorrect number of data, c = %d, dst = %d\n",
|
|
count, count_dst);
|
|
goto error;
|
|
}
|
|
total -= count;
|
|
s = 0;
|
|
}
|
|
|
|
key_clean = dma_alloc_coherent(dev, DMA_BUF_SIZE, &cleanup_addr,
|
|
GFP_KERNEL | GFP_DMA);
|
|
if (!key_clean) {
|
|
dbgp(2, "cannot allocate key_clean\n");
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
memset(key_clean, 0, DMA_BUF_SIZE);
|
|
dsc[0].src_addr = cleanup_addr;
|
|
dsc[0].tgt_addr = 0;
|
|
dsc[0].dsc_cfg.d32 = 0;
|
|
dsc[0].dsc_cfg.b.length = DMA_BUF_SIZE;
|
|
dsc[0].dsc_cfg.b.mode = MODE_KEY;
|
|
dsc[0].dsc_cfg.b.eoc = 1;
|
|
dsc[0].dsc_cfg.b.owner = 1;
|
|
|
|
lock_ret = mutex_lock_interruptible(&crypto_dd->lock);
|
|
if (lock_ret)
|
|
goto error;
|
|
crypto_dd->dma_busy = 1;
|
|
crypto_dd->processing = current;
|
|
aml_dma_debug(dsc, 1, __func__, crypto_dd->thread, crypto_dd->status);
|
|
aml_write_crypto_reg(crypto_dd->thread, (uintptr_t)dsc_addr | 2);
|
|
#if USE_BUSY_POLLING
|
|
while ((err = aml_read_crypto_reg(crypto_dd->status)) == 0)
|
|
;
|
|
aml_write_crypto_reg(crypto_dd->status, 0xf);
|
|
#else
|
|
schedule();
|
|
err = crypto_dd->err;
|
|
#endif
|
|
crypto_dd->dma_busy = 0;
|
|
mutex_unlock(&crypto_dd->lock);
|
|
if (err & DMA_STATUS_KEY_ERROR) {
|
|
rc = -EACCES;
|
|
goto error;
|
|
}
|
|
|
|
if (ses_ptr->cdata.op_mode == OP_MODE_CBC) {
|
|
if (kcop->cop.op == CRYPTO_OP_ENCRYPT) {
|
|
memcpy(kcop->iv, tmp_buf + count_dst -
|
|
ses_ptr->cdata.blocksize,
|
|
ses_ptr->cdata.blocksize);
|
|
}
|
|
} else if (ses_ptr->cdata.op_mode == OP_MODE_CTR) {
|
|
u32 dma_nblock =
|
|
(src_bufsz + ses_ptr->cdata.blocksize - 1)
|
|
/ ses_ptr->cdata.blocksize;
|
|
ctr_update_iv(kcop->iv, dma_nblock);
|
|
}
|
|
|
|
error:
|
|
if (dma_buf)
|
|
dma_unmap_single(dev, dma_buf, PAGE_SIZE, DMA_TO_DEVICE);
|
|
if (iv)
|
|
dma_free_coherent(dev, kcop->ivlen, iv, dma_iv_addr);
|
|
if (dsc)
|
|
dma_free_coherent(dev, 3 * sizeof(struct dma_dsc),
|
|
dsc, dsc_addr);
|
|
if (key_clean)
|
|
dma_free_coherent(dev, DMA_BUF_SIZE,
|
|
key_clean, cleanup_addr);
|
|
if (tmp_buf)
|
|
free_page((uintptr_t)tmp_buf);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int crypto_run(struct fcrypt *fcr, struct kernel_crypt_op *kcop)
|
|
{
|
|
struct crypto_session *ses_ptr;
|
|
struct crypt_op *cop = &kcop->cop;
|
|
int ret = 0;
|
|
|
|
if (unlikely(cop->op != CRYPTO_OP_ENCRYPT &&
|
|
cop->op != CRYPTO_OP_DECRYPT)) {
|
|
dbgp(2, "invalid operation op=%u", cop->op);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* this also enters ses_ptr->sem */
|
|
ses_ptr = crypto_get_session_by_sid(fcr, cop->ses);
|
|
if (unlikely(!ses_ptr)) {
|
|
dbgp(2, "invalid session ID=0x%08X", cop->ses);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (ses_ptr->cdata.init != 0) {
|
|
if (ses_ptr->cdata.cipher == CRYPTO_OP_S17_ECB ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CBC ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_S17_CTR) {
|
|
if (kcop->param && kcop->param_len == 4) {
|
|
u32 s17_cfg = 0;
|
|
|
|
memcpy(&s17_cfg, kcop->param, kcop->param_len);
|
|
s17_cfg = ((s17_cfg & 0x1fff) |
|
|
crypto_dd->thread << 13);
|
|
if (call_smc(CRYPTO_OPERATION_CFG,
|
|
SET_S17_M2M_CFG, s17_cfg, 0)) {
|
|
dbgp(0, "Not support s17 cfg from ree");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ses_ptr->cdata.cipher == CRYPTO_OP_AES_CTR ||
|
|
ses_ptr->cdata.cipher == CRYPTO_OP_SM4_CTR) {
|
|
/* aes-ctr and sm4-ctr only supports encrypt */
|
|
kcop->cop.op = CRYPTO_OP_ENCRYPT;
|
|
}
|
|
if (ses_ptr->cdata.op_mode != OP_MODE_ECB && !kcop->cop.ivlen) {
|
|
dbgp(2, "no iv for non-ecb\n");
|
|
ret = -EINVAL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
if (kcop->cop.src_phys && kcop->cop.dst_phys)
|
|
ret = __crypto_run_physical(ses_ptr, kcop);
|
|
else if (!kcop->cop.src_phys && kcop->cop.dst_phys)
|
|
ret = __crypto_run_virt_to_phys(ses_ptr, kcop);
|
|
else if (kcop->cop.src_phys && !kcop->cop.dst_phys)
|
|
ret = __crypto_run_phys_to_virt(ses_ptr, kcop);
|
|
else
|
|
ret = __crypto_run_virtual(ses_ptr, kcop);
|
|
} else {
|
|
ret = -EINVAL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
out_unlock:
|
|
crypto_put_session(ses_ptr);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int aml_crypto_dev_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct crypt_priv *pcr;
|
|
|
|
pcr = kzalloc(sizeof(*pcr), GFP_KERNEL);
|
|
if (!pcr)
|
|
return -ENOMEM;
|
|
filp->private_data = pcr;
|
|
|
|
mutex_init(&pcr->fcrypt.sem);
|
|
|
|
INIT_LIST_HEAD(&pcr->fcrypt.list);
|
|
return 0;
|
|
}
|
|
|
|
static int aml_crypto_dev_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct crypt_priv *pcr = filp->private_data;
|
|
|
|
if (!pcr)
|
|
return 0;
|
|
|
|
mutex_destroy(&pcr->fcrypt.sem);
|
|
kfree(pcr);
|
|
filp->private_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long aml_crypto_dev_ioctl(struct file *filp,
|
|
unsigned int cmd, unsigned long arg_)
|
|
{
|
|
void __user *arg = (void __user *)arg_;
|
|
struct session_op sop;
|
|
struct kernel_crypt_op kcop;
|
|
struct crypt_priv *pcr = filp->private_data;
|
|
struct fcrypt *fcr;
|
|
u32 ses;
|
|
int ret = 0;
|
|
|
|
if (unlikely(!pcr)) {
|
|
dbgp(2, "empty pcr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fcr = &pcr->fcrypt;
|
|
|
|
switch (cmd) {
|
|
case CREATE_SESSION:
|
|
if (unlikely(copy_from_user(&sop, arg, sizeof(sop))))
|
|
return -EFAULT;
|
|
|
|
ret = crypto_create_session(fcr, &sop);
|
|
if (unlikely(ret))
|
|
return ret;
|
|
ret = copy_to_user(arg, &sop, sizeof(sop));
|
|
if (unlikely(ret)) {
|
|
crypto_finish_session(fcr, sop.ses);
|
|
return -EFAULT;
|
|
}
|
|
return ret;
|
|
case CLOSE_SESSION:
|
|
ret = get_user(ses, (u32 __user *)arg);
|
|
if (unlikely(ret))
|
|
return ret;
|
|
ret = crypto_finish_session(fcr, ses);
|
|
return ret;
|
|
|
|
case DO_CRYPTO:
|
|
ret = kcop_from_user(&kcop, fcr, arg);
|
|
if (unlikely(ret)) {
|
|
dbgp(2, "Error copying from user");
|
|
return ret;
|
|
}
|
|
|
|
ret = crypto_run(fcr, &kcop);
|
|
if (unlikely(ret)) {
|
|
dbgp(2, "Error in crypto_run");
|
|
return ret;
|
|
}
|
|
|
|
return kcop_to_user(&kcop, fcr, arg);
|
|
default:
|
|
dbgp(3, "unknown cmd: %d\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
static inline void compat_to_crypt_op(struct compat_crypt_op *compat,
|
|
struct crypt_op *cop)
|
|
{
|
|
int i = 0;
|
|
#if TEST_PHYSICAL_PROCESS
|
|
struct device *dev = NULL;
|
|
int rc = 0;
|
|
|
|
dev = crypto_dd->dev;
|
|
#endif
|
|
|
|
cop->ses = compat->ses;
|
|
cop->op = compat->op;
|
|
cop->src_phys = compat->src_phys;
|
|
cop->dst_phys = compat->dst_phys;
|
|
cop->ivlen = compat->ivlen;
|
|
cop->num_src_bufs = compat->num_src_bufs;
|
|
cop->num_dst_bufs = compat->num_src_bufs;
|
|
cop->param_len = compat->param_len;
|
|
cop->reserved = 0;
|
|
|
|
for (i = 0; i < MAX_CRYPTO_BUFFERS; i++) {
|
|
if (cop->src_phys) {
|
|
#if TEST_PHYSICAL_PROCESS
|
|
g_src[i] = dma_alloc_coherent(dev,
|
|
compat->src[i].length,
|
|
&g_src_dma[i],
|
|
GFP_KERNEL | GFP_DMA);
|
|
g_tmp_src_addr[i] = compat_ptr(compat->src[i].addr);
|
|
cop->src[i].addr = (u8 *)(uintptr_t)g_src_dma[i];
|
|
cop->src[i].length = compat->src[i].length;
|
|
rc = copy_from_user(g_src[i],
|
|
compat_ptr(compat->src[i].addr),
|
|
compat->src[i].length);
|
|
if (unlikely(rc))
|
|
dev_err(dev, "failed at %s %d\n",
|
|
__func__, __LINE__);
|
|
#else
|
|
cop->src[i].addr = (u8 *)(uintptr_t)compat->src[i].addr;
|
|
cop->src[i].length = compat->src[i].length;
|
|
#endif
|
|
} else {
|
|
cop->src[i].addr = compat_ptr(compat->src[i].addr);
|
|
cop->src[i].length = compat->src[i].length;
|
|
}
|
|
if (cop->dst_phys) {
|
|
#if TEST_PHYSICAL_PROCESS
|
|
g_dst[i] = dma_alloc_coherent(dev,
|
|
compat->dst[i].length,
|
|
&g_dst_dma[i],
|
|
GFP_KERNEL | GFP_DMA);
|
|
g_tmp_dst_addr[i] = compat_ptr(compat->dst[i].addr);
|
|
cop->dst[i].addr = (u8 *)(uintptr_t)g_dst_dma[i];
|
|
cop->dst[i].length = compat->dst[i].length;
|
|
rc = copy_from_user(g_dst[i],
|
|
compat_ptr(compat->dst[i].addr),
|
|
compat->src[i].length);
|
|
if (unlikely(rc))
|
|
dev_err(dev, "failed at %s %d\n",
|
|
__func__, __LINE__);
|
|
#else
|
|
cop->dst[i].addr = (u8 *)(uintptr_t)compat->dst[i].addr;
|
|
cop->dst[i].length = compat->dst[i].length;
|
|
#endif
|
|
} else {
|
|
cop->dst[i].addr = compat_ptr(compat->dst[i].addr);
|
|
cop->dst[i].length = compat->dst[i].length;
|
|
}
|
|
}
|
|
cop->iv = compat_ptr(compat->iv);
|
|
cop->param = compat_ptr(compat->param);
|
|
}
|
|
|
|
static inline void crypt_op_to_compat(struct crypt_op *cop,
|
|
struct compat_crypt_op *compat)
|
|
{
|
|
int i = 0;
|
|
#if TEST_PHYSICAL_PROCESS
|
|
int rc = 0;
|
|
struct device *dev = NULL;
|
|
|
|
dev = crypto_dd->dev;
|
|
#endif
|
|
|
|
compat->ses = cop->ses;
|
|
compat->op = cop->op;
|
|
compat->src_phys = cop->src_phys;
|
|
compat->dst_phys = cop->dst_phys;
|
|
compat->ivlen = cop->ivlen;
|
|
compat->num_src_bufs = cop->num_src_bufs;
|
|
compat->num_dst_bufs = cop->num_src_bufs;
|
|
compat->param_len = cop->param_len;
|
|
compat->reserved = 0;
|
|
|
|
for (i = 0; i < MAX_CRYPTO_BUFFERS; i++) {
|
|
if (cop->src_phys) {
|
|
#if TEST_PHYSICAL_PROCESS
|
|
rc = copy_to_user(g_tmp_src_addr[i],
|
|
g_src[i], cop->src[i].length);
|
|
if (unlikely(rc))
|
|
dev_err(dev, "failed at %s %d\n",
|
|
__func__, __LINE__);
|
|
compat->src[i].addr = ptr_to_compat(g_tmp_src_addr[i]);
|
|
compat->src[i].length = cop->src[i].length;
|
|
dma_free_coherent(dev, cop->src[i].length,
|
|
g_src[i], g_src_dma[i]);
|
|
#else
|
|
compat->src[i].addr = (uintptr_t)cop->src[i].addr;
|
|
compat->src[i].length = cop->src[i].length;
|
|
#endif
|
|
} else {
|
|
compat->src[i].addr = ptr_to_compat(cop->src[i].addr);
|
|
compat->src[i].length = cop->src[i].length;
|
|
}
|
|
if (cop->dst_phys) {
|
|
#if TEST_PHYSICAL_PROCESS
|
|
rc = copy_to_user(g_tmp_dst_addr[i], g_dst[i],
|
|
cop->dst[i].length);
|
|
if (unlikely(rc))
|
|
dev_err(dev, "failed at %s %d\n",
|
|
__func__, __LINE__);
|
|
compat->dst[i].addr = ptr_to_compat(g_tmp_dst_addr[i]);
|
|
compat->dst[i].length = cop->dst[i].length;
|
|
dma_free_coherent(dev, cop->dst[i].length,
|
|
g_dst[i], g_dst_dma[i]);
|
|
#else
|
|
compat->dst[i].addr = (uintptr_t)cop->dst[i].addr;
|
|
compat->dst[i].length = cop->dst[i].length;
|
|
#endif
|
|
} else {
|
|
compat->dst[i].addr = ptr_to_compat(cop->dst[i].addr);
|
|
compat->dst[i].length = cop->dst[i].length;
|
|
}
|
|
}
|
|
compat->iv = ptr_to_compat(cop->iv);
|
|
compat->param = ptr_to_compat(cop->param);
|
|
}
|
|
|
|
static int compat_kcop_from_user(struct kernel_crypt_op *kcop,
|
|
struct fcrypt *fcr, void __user *arg)
|
|
{
|
|
struct compat_crypt_op compat_cop;
|
|
|
|
if (unlikely(copy_from_user(&compat_cop, arg, sizeof(compat_cop))))
|
|
return -EFAULT;
|
|
compat_to_crypt_op(&compat_cop, &kcop->cop);
|
|
|
|
if (kcop->cop.num_src_bufs > MAX_CRYPTO_BUFFERS ||
|
|
kcop->cop.num_dst_bufs > MAX_CRYPTO_BUFFERS) {
|
|
dbgp(2, "too many buffers, src: %d, dst: %d\n",
|
|
kcop->cop.num_src_bufs, kcop->cop.num_dst_bufs);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return fill_kcop_from_cop(kcop, fcr);
|
|
}
|
|
|
|
static int compat_kcop_to_user(struct kernel_crypt_op *kcop,
|
|
struct fcrypt *fcr, void __user *arg)
|
|
{
|
|
int ret;
|
|
struct compat_crypt_op compat_cop;
|
|
|
|
ret = fill_cop_from_kcop(kcop, fcr);
|
|
if (unlikely(ret)) {
|
|
dbgp(2, "Error in fill_cop_from_kcop");
|
|
return ret;
|
|
}
|
|
crypt_op_to_compat(&kcop->cop, &compat_cop);
|
|
|
|
if (unlikely(copy_to_user(arg, &compat_cop, sizeof(compat_cop)))) {
|
|
dbgp(2, "Error copying to user");
|
|
return -EFAULT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static long aml_crypto_dev_compat_ioctl(struct file *filp,
|
|
unsigned int cmd, unsigned long arg_)
|
|
{
|
|
void __user *arg = (void __user *)arg_;
|
|
struct crypt_priv *pcr = filp->private_data;
|
|
struct fcrypt *fcr;
|
|
struct kernel_crypt_op kcop;
|
|
int ret = 0;
|
|
|
|
if (unlikely(!pcr)) {
|
|
dbgp(2, "empty pcr\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fcr = &pcr->fcrypt;
|
|
|
|
switch (cmd) {
|
|
case CREATE_SESSION:
|
|
fallthrough;
|
|
case CLOSE_SESSION:
|
|
return aml_crypto_dev_ioctl(filp, cmd, arg_);
|
|
case DO_CRYPTO_COMPAT:
|
|
ret = compat_kcop_from_user(&kcop, fcr, arg);
|
|
if (unlikely(ret))
|
|
return ret;
|
|
|
|
ret = crypto_run(fcr, &kcop);
|
|
if (unlikely(ret))
|
|
return ret;
|
|
|
|
return compat_kcop_to_user(&kcop, fcr, arg);
|
|
default:
|
|
dbgp(3, "unknown cmd: %d\n", cmd);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static const struct file_operations aml_crypto_dev_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = aml_crypto_dev_open,
|
|
.release = aml_crypto_dev_release,
|
|
.unlocked_ioctl = aml_crypto_dev_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = aml_crypto_dev_compat_ioctl,
|
|
#endif /* CONFIG_COMPAT */
|
|
};
|
|
|
|
static struct miscdevice aml_crypto_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "aml_crypto",
|
|
.fops = &aml_crypto_dev_fops,
|
|
.mode = 0666,
|
|
};
|
|
|
|
static int aml_crypto_dev_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
const struct of_device_id *match;
|
|
struct resource *res_irq = 0;
|
|
const struct meson_crypto_dev_data *priv_data = NULL;
|
|
int err = 0;
|
|
u8 thread = 0;
|
|
|
|
crypto_dd = devm_kzalloc(dev, sizeof(struct aml_crypto_dev),
|
|
GFP_KERNEL);
|
|
if (!crypto_dd) {
|
|
err = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
match = of_match_device(aml_crypto_dev_dt_match, &pdev->dev);
|
|
if (!match) {
|
|
dev_err(dev, "%s: cannot find match dt\n", __func__);
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
|
|
of_property_read_u8(pdev->dev.of_node, "thread", &thread);
|
|
|
|
priv_data = match->data;
|
|
crypto_dd->dev = dev;
|
|
crypto_dd->thread = thread;
|
|
crypto_dd->status = priv_data->status + thread;
|
|
crypto_dd->algo_cap = priv_data->algo_cap;
|
|
crypto_dd->irq = res_irq->start;
|
|
crypto_dd->processing = NULL;
|
|
mutex_init(&crypto_dd->lock);
|
|
platform_set_drvdata(pdev, crypto_dd);
|
|
|
|
#if !USE_BUSY_POLLING
|
|
err = devm_request_irq(dev, crypto_dd->irq, aml_crypto_dev_irq,
|
|
IRQF_SHARED, "aml-aes", crypto_dd);
|
|
if (err) {
|
|
dev_err(dev, "unable to request aes irq.\n");
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
#endif
|
|
err = misc_register(&aml_crypto_device);
|
|
if (unlikely(err)) {
|
|
dev_err(dev, "registration of /dev/aml_crypto failed\n");
|
|
err = -EPERM;
|
|
goto error;
|
|
}
|
|
|
|
dev_dbg(dev, "Aml crypto device\n");
|
|
|
|
return err;
|
|
|
|
error:
|
|
dev_err(dev, "initialization failed.\n");
|
|
|
|
return err;
|
|
}
|
|
|
|
static int aml_crypto_dev_remove(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
static struct aml_crypto_dev *crypto_dd;
|
|
const struct of_device_id *match;
|
|
|
|
crypto_dd = platform_get_drvdata(pdev);
|
|
if (!crypto_dd)
|
|
return -ENODEV;
|
|
match = of_match_device(aml_crypto_dev_dt_match, &pdev->dev);
|
|
if (!match) {
|
|
dev_err(dev, "%s: cannot find match dt\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if !USE_BUSY_POLLING
|
|
devm_free_irq(dev, crypto_dd->irq, crypto_dd);
|
|
#endif
|
|
|
|
misc_deregister(&aml_crypto_device);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver aml_crypto_dev_driver = {
|
|
.probe = aml_crypto_dev_probe,
|
|
.remove = aml_crypto_dev_remove,
|
|
.driver = {
|
|
.name = "aml_crypto_dev",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = aml_crypto_dev_dt_match,
|
|
},
|
|
};
|
|
|
|
int __init aml_crypto_device_driver_init(void)
|
|
{
|
|
return platform_driver_register(&aml_crypto_dev_driver);
|
|
}
|
|
|
|
void aml_crypto_device_driver_exit(void)
|
|
{
|
|
platform_driver_unregister(&aml_crypto_dev_driver);
|
|
}
|