/* * Exynos FMP cipher driver * * Copyright (C) 2016 Samsung Electronics Co., Ltd. * Authors: Boojin Kim * * 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 #include #include #include #include #include #include #include #include #include #include #include "fmp_test.h" #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: kfree_sensitive(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); if (!priv) { /* ci is fmp_test_data->ci */ fmp->test_data = fdata; ci->ctx = fmp; 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) { memset(bh->b_data, 0, FMP_BLK_SIZE); memcpy(bh->b_data, data, len); set_buffer_dirty(bh); ret = __sync_dirty_buffer(bh, REQ_SYNC); 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); bh->b_end_io = end_buffer_read_sync; 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; } /* 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); kfree_sensitive(fdata); }