blob: cf202c9c27f6be1a9027c236a77e0a6236ee3ef9 [file] [log] [blame]
/*
* Exynos FMP cipher driver
*
* Copyright (C) 2016 Samsung Electronics Co., Ltd.
* Authors: Boojin Kim <boojin.kim@samsung.com>
*
* 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.
*/
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/crypto.h>
#include <linux/buffer_head.h>
#include <linux/genhd.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/blk_types.h>
#include <crypto/fmp.h>
#include <linux/mm_types.h>
#include "fmp_test.h"
#define MAX_RETRY_COUNT (0x100)
#define FIPS_BLOCK_NAME "/dev/block/by-name/fmp_selftest"
/* test block device init for fmp test */
struct fmp_test_data *fmp_test_init(struct exynos_fmp *fmp)
{
struct fmp_test_data *data;
struct device *dev;
struct inode *inode;
struct super_block *sb;
unsigned long blocksize;
unsigned char blocksize_bits;
uint32_t count = 0;
fmode_t fmode = FMODE_WRITE | FMODE_READ;
if (!fmp) {
pr_err("%s: Invalid exynos fmp struct\n", __func__);
goto err;
}
dev = fmp->dev;
data = kmalloc(sizeof(struct fmp_test_data), GFP_KERNEL);
if (!data)
goto err;
do {
data->bdev = blkdev_get_by_path(FIPS_BLOCK_NAME, fmode, NULL);
if (IS_ERR(data->bdev)) {
dev_warn(dev, "retry blkdev_get_by_path = %d\n", count);
mdelay(100);
count++;
continue;
} else {
dev_info(dev, "FOUND fmp fips block = %d\n", count);
break;
}
} while (count < MAX_RETRY_COUNT);
if (IS_ERR(data->bdev)) {
dev_err(dev, "%s: Fail to open block device\n", __func__);
goto err_data;
}
data->test_block_offset = 1;
inode = data->bdev->bd_inode;
sb = inode->i_sb;
blocksize = sb->s_blocksize;
blocksize_bits = sb->s_blocksize_bits;
data->sector =
(i_size_read(inode) -
(blocksize * data->test_block_offset)) >> blocksize_bits;
return data;
err_data:
kzfree(data);
err:
return NULL;
}
int fmp_cipher_run(struct exynos_fmp *fmp, struct fmp_test_data *fdata,
uint8_t *data, uint32_t len, bool bypass, uint32_t write,
void *priv, struct fmp_crypto_info *ci)
{
int ret = 0;
struct device *dev;
static struct buffer_head *bh;
u32 org_algo_mode;
if (!fmp || !fdata || !ci) {
pr_err("%s: Invalid fmp struct(fmp, fdata, ci)\n", __func__);
return -EINVAL;
}
dev = fmp->dev;
bh = __getblk(fdata->bdev, fdata->sector, FMP_BLK_SIZE);
if (!bh) {
dev_err(dev, "%s: Fail to get block from bdev\n", __func__);
return -ENODEV;
}
/* set algo_mode for test */
org_algo_mode = ci->algo_mode;
if (bypass)
ci->algo_mode = EXYNOS_FMP_BYPASS_MODE;
ci->algo_mode |= EXYNOS_FMP_ALGO_MODE_TEST;
get_bh(bh);
/* priv is diskc for crypto test. */
if (!priv) {
/* ci is fmp_test_data->ci */
fmp->test_data = fdata;
ci->ctx = fmp;
ci->use_diskc = 0;
ci->enc_mode = EXYNOS_FMP_FILE_ENC;
bh->b_private = fmp;
fmp->bh = bh;
fmp->fips_run++;
} else {
/* ci is crypto_tfm_ctx(tfm) */
bh->b_private = priv;
}
if (write == WRITE_MODE) {
memcpy(bh->b_data, data, len);
set_buffer_dirty(bh);
ret = __sync_dirty_buffer(bh, REQ_SYNC | REQ_FUA);
if (ret) {
dev_err(dev, "%s: IO error syncing for write mode\n",
__func__);
ret = -EIO;
goto out;
}
memset(bh->b_data, 0, FMP_BLK_SIZE);
} else {
lock_buffer(bh);
fmp_set_bh_compl_handler(bh);
submit_bh(REQ_OP_READ, REQ_SYNC | REQ_PRIO, bh);
wait_on_buffer(bh);
if (unlikely(!buffer_uptodate(bh))) {
ret = -EIO;
goto out;
}
memcpy(data, bh->b_data, len);
}
out:
if (ci)
ci->algo_mode = org_algo_mode;
put_bh(bh);
fmp->bh = NULL;
return ret;
}
int fmp_test_crypt(struct exynos_fmp *fmp, struct fmp_test_data *fdata,
uint8_t *src, uint8_t *dst, uint32_t len, uint32_t enc,
void *priv, struct fmp_crypto_info *ci)
{
int ret = 0;
if (!fdata) {
pr_err("%s: Invalid exynos fmp struct\n", __func__);
return -1;
}
if (enc == ENCRYPT) {
ret = fmp_cipher_run(fmp, fdata, src, len, 0,
WRITE_MODE, priv, ci);
if (ret) {
pr_err("Fail to run fmp cipher ret(%d)\n",
ret);
goto err;
}
ret = fmp_cipher_run(fmp, fdata, dst, len, 1,
READ_MODE, priv, ci);
if (ret) {
pr_err("Fail to run fmp cipher ret(%d)\n",
ret);
goto err;
}
} else if (enc == DECRYPT) {
ret = fmp_cipher_run(fmp, fdata, src, len, 1,
WRITE_MODE, priv, ci);
if (ret) {
pr_err("Fail to run fmp cipher ret(%d)\n",
ret);
goto err;
}
ret = fmp_cipher_run(fmp, fdata, dst, len, 0,
READ_MODE, priv, ci);
if (ret) {
pr_err("Fail to run fmp cipher ret(%d)\n",
ret);
goto err;
}
} else {
pr_err("%s: Invalid enc %d mode\n", __func__, enc);
goto err;
}
return 0;
err:
return -EINVAL;
}
/* test block device release for fmp test */
void fmp_test_exit(struct fmp_test_data *fdata)
{
fmode_t fmode = FMODE_WRITE | FMODE_READ;
if (!fdata) {
pr_err("%s: Invalid exynos fmp struct\n", __func__);
return;
}
if (fdata->bdev)
blkdev_put(fdata->bdev, fmode);
kzfree(fdata);
}