895 lines
22 KiB
C
895 lines
22 KiB
C
|
/*
|
||
|
* Exynos FMP driver
|
||
|
*
|
||
|
* Copyright (C) 2015 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 <asm/unaligned.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <asm/cacheflush.h>
|
||
|
#include <linux/crypto.h>
|
||
|
#include <linux/dma-mapping.h>
|
||
|
#include <soc/samsung/exynos-smc.h>
|
||
|
#include <crypto/aes.h>
|
||
|
#include <crypto/algapi.h>
|
||
|
#include <crypto/fmp.h>
|
||
|
|
||
|
#include "fmp_fips_main.h"
|
||
|
#include "fmp_test.h"
|
||
|
#include "fmp_fips_info.h"
|
||
|
#ifndef CONFIG_KEYS_IN_PRDT
|
||
|
#include "ufs-vs-mmio.h"
|
||
|
#endif
|
||
|
#include <linux/genhd.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/buffer_head.h>
|
||
|
|
||
|
#define WORD_SIZE 4
|
||
|
#define FMP_IV_MAX_IDX (FMP_IV_SIZE_16 / WORD_SIZE)
|
||
|
|
||
|
#define byte2word(b0, b1, b2, b3) \
|
||
|
(((unsigned int)(b0) << 24) | \
|
||
|
((unsigned int)(b1) << 16) | \
|
||
|
((unsigned int)(b2) << 8) | (b3))
|
||
|
#define get_word(x, c) byte2word(((unsigned char *)(x) + 4 * (c))[0], \
|
||
|
((unsigned char *)(x) + 4 * (c))[1], \
|
||
|
((unsigned char *)(x) + 4 * (c))[2], \
|
||
|
((unsigned char *)(x) + 4 * (c))[3])
|
||
|
/* Legacy Operation */
|
||
|
#define FKL BIT(26)
|
||
|
#define DKL BIT(27)
|
||
|
#define SET_KEYLEN(d, v) ((d)->des3 |= (uint32_t)v)
|
||
|
#define SET_FAS(d, v) \
|
||
|
((d)->des3 = ((d)->des3 & 0xcfffffff) | v << 28)
|
||
|
#define SET_DAS(d, v) \
|
||
|
((d)->des3 = ((d)->des3 & 0x3fffffff) | v << 30)
|
||
|
#define GET_FAS(d) ((d)->des3 & 0x30000000)
|
||
|
#define GET_DAS(d) ((d)->des3 & 0xc0000000)
|
||
|
#define GET_LENGTH(d) \
|
||
|
((d)->des3 & 0x3ffffff)
|
||
|
|
||
|
static struct device *fmp_dev;
|
||
|
|
||
|
struct exynos_fmp *get_fmp(void)
|
||
|
{
|
||
|
return dev_get_drvdata(fmp_dev);
|
||
|
}
|
||
|
EXPORT_SYMBOL(get_fmp);
|
||
|
|
||
|
static inline void dump_ci(struct fmp_crypto_info *c)
|
||
|
{
|
||
|
if (c) {
|
||
|
pr_info
|
||
|
("%s: algo:%d enc:%d key_size:%d\n",
|
||
|
__func__, c->algo_mode, c->enc_mode,c->key_size);
|
||
|
if (c->enc_mode == EXYNOS_FMP_FILE_ENC)
|
||
|
print_hex_dump(KERN_CONT, "key:",
|
||
|
DUMP_PREFIX_OFFSET, 16, 1, c->key,
|
||
|
sizeof(c->key), false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline void dump_table(struct fmp_table_setting *table)
|
||
|
{
|
||
|
print_hex_dump(KERN_CONT, "dump:", DUMP_PREFIX_OFFSET, 16, 1,
|
||
|
table, sizeof(struct fmp_table_setting), false);
|
||
|
}
|
||
|
|
||
|
static inline int is_supported_ivsize(u32 ivlen)
|
||
|
{
|
||
|
if (ivlen && (ivlen <= FMP_IV_SIZE_16))
|
||
|
return TRUE;
|
||
|
else
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static inline int check_aes_xts_size(struct fmp_table_setting *table,
|
||
|
bool cmdq_enabled)
|
||
|
{
|
||
|
int size;
|
||
|
|
||
|
if (cmdq_enabled)
|
||
|
size = GET_CMDQ_LENGTH(table);
|
||
|
else
|
||
|
size = GET_LENGTH(table);
|
||
|
return (size > MAX_AES_XTS_TRANSFER_SIZE) ? size : 0;
|
||
|
}
|
||
|
|
||
|
/* check for fips that no allow same keys */
|
||
|
static inline int check_aes_xts_key(char *key,
|
||
|
enum fmp_crypto_key_size key_size)
|
||
|
{
|
||
|
char *enckey, *twkey;
|
||
|
|
||
|
enckey = key;
|
||
|
twkey = key + key_size;
|
||
|
return (memcmp(enckey, twkey, key_size)) ? 0 : -1;
|
||
|
}
|
||
|
|
||
|
int fmplib_set_algo_mode(struct fmp_table_setting *table,
|
||
|
struct fmp_crypto_info *crypto, bool cmdq_enabled)
|
||
|
{
|
||
|
int ret;
|
||
|
enum fmp_crypto_fips_algo_mode algo_mode = crypto->algo_mode & EXYNOS_FMP_ALGO_MODE_MASK;
|
||
|
|
||
|
if (algo_mode == EXYNOS_FMP_ALGO_MODE_AES_XTS) {
|
||
|
ret = check_aes_xts_size(table, cmdq_enabled);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail FMP XTS due to invalid size(%d), cmdq:%d\n",
|
||
|
__func__, ret, cmdq_enabled);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (crypto->enc_mode) {
|
||
|
case EXYNOS_FMP_FILE_ENC:
|
||
|
if (cmdq_enabled)
|
||
|
SET_CMDQ_FAS(table, algo_mode);
|
||
|
else
|
||
|
SET_FAS(table, algo_mode);
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("%s: Invalid fmp enc mode %d\n", __func__,
|
||
|
crypto->enc_mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
#ifdef CONFIG_KEYS_IN_PRDT
|
||
|
static inline __be32 fmp_key_word(const u8 *key, int j)
|
||
|
{
|
||
|
return cpu_to_be32(get_unaligned_le32(
|
||
|
key + AES_KEYSIZE_256 - (j + 1) * sizeof(__le32)));
|
||
|
}
|
||
|
|
||
|
static int fmplib_set_file_key(struct fmp_table_setting *table,
|
||
|
struct fmp_crypto_info *crypto)
|
||
|
{
|
||
|
enum fmp_crypto_fips_algo_mode algo_mode = crypto->algo_mode & EXYNOS_FMP_ALGO_MODE_MASK;
|
||
|
enum fmp_crypto_key_size key_size = crypto->fmp_key_size;
|
||
|
char *key = crypto->key;
|
||
|
int idx, max;
|
||
|
|
||
|
if (!key || (crypto->enc_mode != EXYNOS_FMP_FILE_ENC) ||
|
||
|
((key_size != EXYNOS_FMP_KEY_SIZE_16) &&
|
||
|
(key_size != EXYNOS_FMP_KEY_SIZE_32))) {
|
||
|
pr_err("%s: Invalid key_size:%d enc_mode:%d\n",
|
||
|
__func__, key_size, crypto->enc_mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if ((algo_mode == EXYNOS_FMP_ALGO_MODE_AES_XTS)
|
||
|
&& check_aes_xts_key(key, key_size)) {
|
||
|
pr_err("%s: Fail FMP XTS due to the same enc and twkey\n",
|
||
|
__func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (algo_mode == EXYNOS_FMP_ALGO_MODE_AES_CBC) {
|
||
|
max = key_size / WORD_SIZE;
|
||
|
for (idx = 0; idx < max; idx++)
|
||
|
*(&table->file_enckey0 + idx) =
|
||
|
get_word(key, max - (idx + 1));
|
||
|
} else if (algo_mode == EXYNOS_FMP_ALGO_MODE_AES_XTS) {
|
||
|
key_size *= 2;
|
||
|
max = key_size / WORD_SIZE;
|
||
|
for (idx = 0; idx < (max / 2); idx++)
|
||
|
*(&table->file_enckey0 + idx) =
|
||
|
get_word(key, (max / 2) - (idx + 1));
|
||
|
for (idx = 0; idx < (max / 2); idx++)
|
||
|
*(&table->file_twkey0 + idx) =
|
||
|
get_word(key, max - (idx + 1));
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
#else
|
||
|
static int fmplib_set_file_key(struct fmp_handle *handle, struct fmp_crypto_info *ci)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
enum fmp_crypto_key_size key_size = ci->fmp_key_size;
|
||
|
enum fmp_crypto_fips_algo_mode algo_mode = ci->algo_mode & EXYNOS_FMP_ALGO_MODE_MASK;
|
||
|
struct exynos_fmp_key_info fmp_key_info;
|
||
|
|
||
|
if (IS_ERR_OR_NULL(ci->key) || (ci->enc_mode != EXYNOS_FMP_FILE_ENC) ||
|
||
|
((key_size != EXYNOS_FMP_KEY_SIZE_32))) {
|
||
|
pr_err("%s: Invalid key_size:%d enc_mode:%d\n",
|
||
|
__func__, key_size, ci->enc_mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if ((algo_mode == EXYNOS_FMP_ALGO_MODE_AES_XTS)
|
||
|
&& check_aes_xts_key(ci->key, key_size)) {
|
||
|
pr_err("%s: Fail FMP XTS due to the same enc and twkey\n",
|
||
|
__func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
fmp_key_info.raw = ci->key;
|
||
|
fmp_key_info.size = ci->fmp_key_size * 2;
|
||
|
fmp_key_info.slot = EXYNOS_FMP_FIPS_KEYSLOT;
|
||
|
|
||
|
ret = exynos_fmp_setkey(&fmp_key_info, handle);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to set FMP key in keyslot (ret: %d)\n", __func__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
unsigned long exynos_fmp_set_kw_mode(uint64_t kw_mode)
|
||
|
{
|
||
|
unsigned long ret;
|
||
|
|
||
|
ret = exynos_smc(SMC_CMD_FMP_KW_MODE, 0, FMP_EMBEDDED, kw_mode);
|
||
|
if (ret) {
|
||
|
pr_err("%s: SMC_CMD_FMP_KW_MODE failed to set kw_mode: %ld\n", __func__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
unsigned long exynos_fmp_get_kw_mode(dma_addr_t kw_mode_addr)
|
||
|
{
|
||
|
unsigned long ret;
|
||
|
|
||
|
ret = exynos_smc(SMC_CMD_FMP_GET_KW_MODE, 0, FMP_EMBEDDED, kw_mode_addr);
|
||
|
if (ret) {
|
||
|
pr_err("%s: SMC_CMD_FMP_GET_KW_MODE failed to set kw_mode: %ld\n", __func__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif /* CONFIG_KEYS_IN_PRDT */
|
||
|
|
||
|
#ifdef CONFIG_KEYS_IN_PRDT
|
||
|
static int fmplib_set_key_size(struct fmp_table_setting *table,
|
||
|
struct fmp_crypto_info *crypto, bool cmdq_enabled)
|
||
|
{
|
||
|
enum fmp_crypto_key_size key_size;
|
||
|
|
||
|
key_size = crypto->fmp_key_size;
|
||
|
|
||
|
if ((key_size != EXYNOS_FMP_KEY_SIZE_16) &&
|
||
|
(key_size != EXYNOS_FMP_KEY_SIZE_32)) {
|
||
|
pr_err("%s: Invalid keysize %d\n", __func__, key_size);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
switch (crypto->enc_mode) {
|
||
|
case EXYNOS_FMP_FILE_ENC:
|
||
|
if (cmdq_enabled)
|
||
|
SET_CMDQ_KEYLEN(table,
|
||
|
(key_size ==
|
||
|
FMP_KEY_SIZE_32) ? FKL_CMDQ : 0);
|
||
|
else
|
||
|
SET_KEYLEN(table,
|
||
|
(key_size == FMP_KEY_SIZE_32) ? FKL : 0);
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("%s: Invalid fmp enc mode %d\n", __func__,
|
||
|
crypto->enc_mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int fmplib_set_iv(struct fmp_table_setting *table,
|
||
|
struct fmp_crypto_info *crypto, u8 *iv)
|
||
|
{
|
||
|
int idx;
|
||
|
|
||
|
switch (crypto->enc_mode) {
|
||
|
case EXYNOS_FMP_FILE_ENC:
|
||
|
for (idx = 0; idx < FMP_IV_MAX_IDX; idx++)
|
||
|
*(&table->file_iv0 + idx) =
|
||
|
get_word(iv, FMP_IV_MAX_IDX - (idx + 1));
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("%s: Invalid fmp enc mode %d\n", __func__,
|
||
|
crypto->enc_mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int exynos_fmp_crypt(struct exynos_fmp_crypt_info *fmp_ci, struct fmp_table_setting *table)
|
||
|
{
|
||
|
struct exynos_fmp *fmp = get_fmp();
|
||
|
struct fmp_crypto_info *ci;
|
||
|
struct fmp_sg_entry *ent = (struct fmp_sg_entry *)table;
|
||
|
u8 iv[FMP_IV_SIZE_16];
|
||
|
u64 ret = 0;
|
||
|
size_t j, limit;
|
||
|
|
||
|
if (!fmp) {
|
||
|
pr_err("%s: invalid fmp\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!table) {
|
||
|
pr_err("%s: invalid table setting\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (get_fmp_fips_state())
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (fmp_ci->fips) {
|
||
|
/* check fips test data */
|
||
|
if (!fmp->test_data) {
|
||
|
dev_err(fmp->dev, "%s: no test_data for test mode\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ci = &fmp->test_data->ci;
|
||
|
if (!ci || !(ci->algo_mode & EXYNOS_FMP_ALGO_MODE_TEST)) {
|
||
|
dev_err(fmp->dev, "%s: Invalid fmp crypto_info for test mode\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!(ci->algo_mode & EXYNOS_FMP_ALGO_MODE_MASK)) {
|
||
|
dev_err(fmp->dev, "%s: no test_data for algo mode\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = fmplib_set_algo_mode(table, ci, CMDQ_DISABLED);
|
||
|
if (ret) {
|
||
|
dev_err(fmp->dev, "%s: Fail to set FMP encryption mode\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* set key into table */
|
||
|
switch (ci->enc_mode) {
|
||
|
case EXYNOS_FMP_FILE_ENC:
|
||
|
ret = fmplib_set_file_key(table, ci);
|
||
|
if (ret) {
|
||
|
dev_err(fmp->dev, "%s: Fail to set FMP key\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(fmp->dev, "%s: Invalid fmp enc mode %d\n", __func__, ci->enc_mode);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* set key size into table */
|
||
|
ret = fmplib_set_key_size(table, ci, CMDQ_DISABLED);
|
||
|
if (ret) {
|
||
|
dev_err(fmp->dev, "%s: Fail to set FMP key size\n", __func__);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* use test manager's iv instead of host driver's iv */
|
||
|
dev_dbg(fmp->dev, "%s: fips crypt: ivsize:%d, key_size: %d, %d\n",
|
||
|
__func__, sizeof(fmp->test_data->iv), ci->key_size, ci->fmp_key_size);
|
||
|
|
||
|
if (!is_supported_ivsize(sizeof(fmp->test_data->iv))) {
|
||
|
dev_err(fmp->dev, "%s: invalid (mode:%d ivsize:%d)\n",
|
||
|
__func__, ci->algo_mode & EXYNOS_FMP_ALGO_MODE_MASK,
|
||
|
sizeof(fmp->test_data->iv));
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* set iv as intended size */
|
||
|
memset(iv, 0, FMP_IV_SIZE_16);
|
||
|
memcpy(iv, fmp->test_data->iv, sizeof(fmp->test_data->iv));
|
||
|
ret = fmplib_set_iv(table, ci, iv);
|
||
|
if (ret) {
|
||
|
dev_err(fmp->dev, "%s: Fail to set FMP IV\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
} else {
|
||
|
if (!crypto_memneq(fmp_ci->enckey, fmp_ci->twkey, AES_KEYSIZE_256)) {
|
||
|
dev_err(fmp->dev, "Can't use weak AES-XTS key\n");
|
||
|
ret = -EKEYREJECTED;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
SET_FAS(ent, EXYNOS_FMP_ALGO_MODE_AES_XTS);
|
||
|
SET_KEYLEN(ent, FKL);
|
||
|
|
||
|
/* set the file/tweak key in table */
|
||
|
for (j = 0, limit = AES_KEYSIZE_256 / sizeof(u32); j < limit; j++) {
|
||
|
ent->file_enckey[j] = fmp_key_word(fmp_ci->enckey, j);
|
||
|
ent->file_twkey[j] = fmp_key_word(fmp_ci->twkey, j);
|
||
|
}
|
||
|
|
||
|
/* Set the IV. */
|
||
|
ent->file_iv[0] = cpu_to_be32(upper_32_bits(fmp_ci->dun_hi));
|
||
|
ent->file_iv[1] = cpu_to_be32(lower_32_bits(fmp_ci->dun_hi));
|
||
|
ent->file_iv[2] = cpu_to_be32(upper_32_bits(fmp_ci->dun_lo));
|
||
|
ent->file_iv[3] = cpu_to_be32(lower_32_bits(fmp_ci->dun_lo));
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
if (ret) {
|
||
|
dump_ci(ci);
|
||
|
dump_table(table);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL(exynos_fmp_crypt);
|
||
|
#else
|
||
|
|
||
|
int exynos_fmp_setkey(struct exynos_fmp_key_info *fmp_ki, struct fmp_handle *handle)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
size_t i, limit;
|
||
|
u32 count = 0;
|
||
|
u32 kw_keyvalid;
|
||
|
u32 slot_offset;
|
||
|
const u8 *enckey, *twkey;
|
||
|
union {
|
||
|
u8 bytes[AES_256_XTS_KEY_SIZE];
|
||
|
u32 words[AES_256_XTS_KEY_SIZE / sizeof(u32)];
|
||
|
} fmp_key;
|
||
|
|
||
|
/* Key length check. FMP only support 256b Key for AES-XTS */
|
||
|
if (fmp_ki->size != AES_256_XTS_KEY_SIZE) {
|
||
|
pr_err("%s: Does not support %d length of AES-XTS key\n", __func__, fmp_ki->size);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (get_fmp_fips_state())
|
||
|
return -EINVAL;
|
||
|
|
||
|
/* In XTS mode, the blk_crypto_key's size is already doubled */
|
||
|
memcpy(fmp_key.bytes, fmp_ki->raw, fmp_ki->size);
|
||
|
|
||
|
enckey = fmp_ki->raw;
|
||
|
twkey = enckey + AES_KEYSIZE_256;
|
||
|
|
||
|
slot_offset = FMP_KW_SECUREKEY + (fmp_ki->slot * FMP_KW_SECUREKEY_OFFSET);
|
||
|
|
||
|
/* Reject weak AES-XTS keys */
|
||
|
if (!crypto_memneq(enckey, twkey, AES_KEYSIZE_256)) {
|
||
|
pr_err("%s: Can't use weak AES-XTS key\n", __func__);
|
||
|
return -EKEYREJECTED;
|
||
|
}
|
||
|
|
||
|
/* Key program in keyslot */
|
||
|
/* Swap File key and Tweak key */
|
||
|
for (i = 0, limit = fmp_ki->size / (sizeof(u32) * 2); i < limit; i++) {
|
||
|
ufsp_writel((struct ufs_vs_handle *)handle, le32_to_cpu(fmp_key.words[i]),
|
||
|
slot_offset + ((i + AES_256_XTS_TWK_OFFSET) * sizeof(u32)));
|
||
|
ufsp_writel((struct ufs_vs_handle *)handle, le32_to_cpu(fmp_key.words[i + AES_256_XTS_TWK_OFFSET]),
|
||
|
slot_offset + (i * sizeof(u32)));
|
||
|
}
|
||
|
|
||
|
/* Zeroise the key */
|
||
|
memzero_explicit(&fmp_key, fmp_ki->size);
|
||
|
|
||
|
/* Keyslot should be valid for crypto IO */
|
||
|
do {
|
||
|
kw_keyvalid = ufsp_readl((struct ufs_vs_handle *)handle, FMP_KW_KEYVALID);
|
||
|
if (!(kw_keyvalid & (0x1 << (fmp_ki->slot)))) {
|
||
|
pr_warn("%s: Key slot #%d is not valid yet\n", __func__, fmp_ki->slot);
|
||
|
udelay(2);
|
||
|
count++;
|
||
|
continue;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
} while (count < MAX_RETRY_COUNT);
|
||
|
|
||
|
if (!(kw_keyvalid & (0x1 << fmp_ki->slot))) {
|
||
|
pr_err("%s: Key slot #%d is not valid\n", __func__, fmp_ki->slot);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
pr_debug("%s: Key valid = 0x%x\n", __func__, kw_keyvalid);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL(exynos_fmp_setkey);
|
||
|
|
||
|
int exynos_fmp_crypt(struct exynos_fmp_crypt_info *fmp_ci, struct fmp_handle *handle)
|
||
|
{
|
||
|
struct exynos_fmp *fmp = get_fmp();
|
||
|
struct fmp_crypto_info *ci;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!fmp) {
|
||
|
pr_err("%s: invalid fmp\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!fmp_ci || !handle) {
|
||
|
dev_err(fmp->dev, "%s: Invalid fmp_crypt_info, fmp_handle parameter\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (get_fmp_fips_state())
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (fmp_ci->fips) {
|
||
|
/* check fips test data */
|
||
|
if (!fmp->test_data) {
|
||
|
dev_err(fmp->dev, "%s: no test_data for test mode\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ci = &fmp->test_data->ci;
|
||
|
if (!ci || !(ci->algo_mode & EXYNOS_FMP_ALGO_MODE_TEST)) {
|
||
|
dev_err(fmp->dev, "%s: Invalid fmp crypto_info for test mode\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!(ci->algo_mode & EXYNOS_FMP_ALGO_MODE_MASK)) {
|
||
|
dev_err(fmp->dev, "%s: no test_data for algo mode\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
switch (ci->enc_mode) {
|
||
|
case EXYNOS_FMP_FILE_ENC:
|
||
|
ret = fmplib_set_file_key(handle, ci);
|
||
|
if (ret) {
|
||
|
dev_err(fmp->dev, "%s: Fail to set FMP key\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(fmp->dev, "%s: Invalid fmp enc mode %d\n", __func__, ci->enc_mode);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (fmp->dun_swap == 1) {
|
||
|
fmp_ci->data_unit_num = cpu_to_be64(fmp->test_data->DataUnitSeqNumber);
|
||
|
fmp_ci->crypto_key_slot = EXYNOS_FMP_FIPS_KEYSLOT;
|
||
|
} else {
|
||
|
fmp_ci->data_unit_num = cpu_to_le64(fmp->test_data->DataUnitSeqNumber);
|
||
|
fmp_ci->crypto_key_slot = EXYNOS_FMP_FIPS_KEYSLOT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL(exynos_fmp_crypt);
|
||
|
#endif /* CONFIG_KEYS_IN_PRDT */
|
||
|
|
||
|
#ifdef CONFIG_KEYS_IN_PRDT
|
||
|
static inline void fmplib_clear_file_key(struct fmp_table_setting *table)
|
||
|
{
|
||
|
memset(&table->file_iv0, 0, sizeof(__le32) * 24);
|
||
|
}
|
||
|
#else
|
||
|
static void fmplib_clear_file_key(struct ufs_vs_handle *handle, int slot)
|
||
|
{
|
||
|
size_t i, limit;
|
||
|
int count = 0;
|
||
|
u32 kw_keyvalid;
|
||
|
u32 slot_offset = FMP_KW_SECUREKEY + (slot * FMP_KW_SECUREKEY_OFFSET);
|
||
|
|
||
|
union {
|
||
|
u8 bytes[AES_256_XTS_KEY_SIZE];
|
||
|
u32 words[AES_256_XTS_KEY_SIZE / sizeof(u32)];
|
||
|
} fmp_key;
|
||
|
|
||
|
if (slot > EXYNOS_FMP_FIPS_KEYSLOT)
|
||
|
return;
|
||
|
|
||
|
/* Make Zeroised key */
|
||
|
memzero_explicit(&fmp_key, AES_256_XTS_KEY_SIZE);
|
||
|
|
||
|
for (i = 0, limit = AES_256_XTS_KEY_SIZE / (sizeof(u32) * 2); i < limit; i++) {
|
||
|
ufsp_writel(handle, le32_to_cpu(fmp_key.words[i]),
|
||
|
slot_offset + ((i + AES_256_XTS_TWK_OFFSET) * sizeof(u32)));
|
||
|
ufsp_writel(handle, le32_to_cpu(fmp_key.words[i + AES_256_XTS_TWK_OFFSET]),
|
||
|
slot_offset + (i * sizeof(u32)));
|
||
|
}
|
||
|
/* Keyslot should be valid for crypto IO */
|
||
|
do {
|
||
|
kw_keyvalid = ufsp_readl(handle, FMP_KW_KEYVALID);
|
||
|
if (!(kw_keyvalid & (0x1 << slot))) {
|
||
|
pr_warn("%s: Key slot #%d is not zeroised yet - 0x%x\n",
|
||
|
__func__, slot, kw_keyvalid);
|
||
|
udelay(2);
|
||
|
count++;
|
||
|
continue;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
} while (count < MAX_RETRY_COUNT);
|
||
|
|
||
|
if (!(kw_keyvalid & (0x1 << slot))) {
|
||
|
pr_err("%s: Key slot #%d is not zeroised\n", __func__, slot);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pr_debug("%s: Key valid = 0x%x\n", __func__, kw_keyvalid);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#ifndef CONFIG_KEYS_IN_PRDT
|
||
|
int exynos_fmp_clear(struct fmp_handle *handle, int slot)
|
||
|
{
|
||
|
struct exynos_fmp *fmp = get_fmp();
|
||
|
|
||
|
if (slot > EXYNOS_FMP_FIPS_KEYSLOT) {
|
||
|
dev_err(fmp->dev, "%s: unable to clear %d keyslot. Keyslot num exceeded\n",
|
||
|
__func__, slot);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!handle) {
|
||
|
dev_err(fmp->dev, "%s: vacant ufs handle\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
fmplib_clear_file_key((struct ufs_vs_handle *)handle, slot);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(exynos_fmp_clear);
|
||
|
#endif /* CONFIG_KEYS_IN_PRDT */
|
||
|
|
||
|
static void fmplib_bypass(void *desc, bool cmdq_enabled)
|
||
|
{
|
||
|
if (cmdq_enabled) {
|
||
|
SET_CMDQ_FAS((struct fmp_table_setting *)desc, 0);
|
||
|
SET_CMDQ_DAS((struct fmp_table_setting *)desc, 0);
|
||
|
} else {
|
||
|
SET_FAS((struct fmp_table_setting *)desc, 0);
|
||
|
SET_DAS((struct fmp_table_setting *)desc, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool fmp_check_fips(struct bio *bio, struct exynos_fmp *fmp)
|
||
|
{
|
||
|
struct page *page;
|
||
|
struct buffer_head *bh;
|
||
|
bool find = false;
|
||
|
struct fmp_crypto_info *ci;
|
||
|
|
||
|
if (bio->bi_io_vec) {
|
||
|
page = bio->bi_io_vec[0].bv_page;
|
||
|
if (page && !PageAnon(page) && page_has_buffers(page)) {
|
||
|
bh = page_buffers(page);
|
||
|
if (bh != fmp->bh) {
|
||
|
dev_err(fmp->dev, "%s: invalid fips bh\n", __func__);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (bh && ((void *)bh->b_private == (void *)fmp))
|
||
|
find = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (find) {
|
||
|
fmp->fips_fin++;
|
||
|
ci = &fmp->test_data->ci;
|
||
|
dev_dbg(fmp->dev, "%s: find fips run(%d) with algo:%d, enc:%d, key_size:%d\n",
|
||
|
__func__, fmp->fips_run, ci->algo_mode, ci->enc_mode, ci->key_size);
|
||
|
if (ci->algo_mode == (EXYNOS_FMP_ALGO_MODE_TEST | EXYNOS_FMP_BYPASS_MODE)) {
|
||
|
dev_dbg(fmp->dev, "%s: find fips for bypass mode\n", __func__);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return find;
|
||
|
}
|
||
|
|
||
|
static bool fmp_check_fips_clean(struct bio *bio, struct exynos_fmp *fmp)
|
||
|
{
|
||
|
bool find = false;
|
||
|
struct page *page;
|
||
|
struct buffer_head *bh;
|
||
|
struct fmp_crypto_info *ci;
|
||
|
|
||
|
if (bio->bi_io_vec) {
|
||
|
page = bio->bi_io_vec[0].bv_page;
|
||
|
if (page && !PageAnon(page) && page_has_buffers(page)) {
|
||
|
bh = page_buffers(page);
|
||
|
if (bh != fmp->bh) {
|
||
|
dev_err(fmp->dev, "%s: invalid fips bh\n", __func__);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (bh && ((void *)bh->b_private == (void *)fmp))
|
||
|
find = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (find) {
|
||
|
fmp->fips_fin--;
|
||
|
ci = &fmp->test_data->ci;
|
||
|
dev_dbg(fmp->dev, "%s: find fips run(%d) fin(%d)with algo:%d, enc:%d, key_size:%d\n",
|
||
|
__func__, fmp->fips_run, fmp->fips_fin, ci->algo_mode, ci->enc_mode, ci->key_size);
|
||
|
if (ci->algo_mode == (EXYNOS_FMP_ALGO_MODE_TEST | EXYNOS_FMP_BYPASS_MODE)) {
|
||
|
dev_dbg(fmp->dev, "%s: find fips for bypass mode\n", __func__);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return find;
|
||
|
}
|
||
|
|
||
|
bool is_fmp_fips_op(struct bio *bio)
|
||
|
{
|
||
|
struct exynos_fmp *fmp = get_fmp();
|
||
|
|
||
|
if (!fmp->fips_run)
|
||
|
return false;
|
||
|
|
||
|
if (!fmp->result.overall && fmp_check_fips(bio, fmp)) {
|
||
|
dev_dbg(fmp->dev, "%s: find fips\n", __func__);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
EXPORT_SYMBOL(is_fmp_fips_op);
|
||
|
|
||
|
bool is_fmp_fips_clean(struct bio *bio)
|
||
|
{
|
||
|
struct exynos_fmp *fmp = get_fmp();
|
||
|
|
||
|
if (fmp->fips_fin && fmp_check_fips_clean(bio, fmp)) {
|
||
|
dev_dbg(fmp->dev, "%s: find fips\n", __func__);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
EXPORT_SYMBOL(is_fmp_fips_clean);
|
||
|
|
||
|
int get_fmp_fips_state(void)
|
||
|
{
|
||
|
if (unlikely(in_fmp_fips_err())) {
|
||
|
#if defined(CONFIG_NODE_FOR_SELFTEST_FAIL)
|
||
|
pr_err("%s: Fail to work fmp config due to fips in error.\n", __func__);
|
||
|
#else
|
||
|
panic("%s: Fail to work fmp config due to fips in error\n", __func__);
|
||
|
#endif
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int exynos_fmp_bypass(struct fmp_table_setting *table, u32 prdt_cnt, unsigned long prdt_off)
|
||
|
{
|
||
|
u32 i;
|
||
|
void *prd = table;
|
||
|
for (i = 0; i < prdt_cnt; i++) {
|
||
|
fmplib_bypass(prd, 0);
|
||
|
prd = (void *)prd + prdt_off;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(exynos_fmp_bypass);
|
||
|
|
||
|
#define CFG_DESCTYPE_3 0x3
|
||
|
static void *exynos_fmp_init(struct platform_device *pdev)
|
||
|
{
|
||
|
struct exynos_fmp *fmp;
|
||
|
struct device_node *np;
|
||
|
u8 dun_swap;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!pdev) {
|
||
|
pr_err("%s: Invalid platform_device.\n", __func__);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
fmp = devm_kzalloc(&pdev->dev, sizeof(struct exynos_fmp), GFP_KERNEL);
|
||
|
if (!fmp)
|
||
|
goto err;
|
||
|
|
||
|
fmp->dev = &pdev->dev;
|
||
|
if (!fmp->dev) {
|
||
|
pr_err("%s: Invalid device.\n", __func__);
|
||
|
goto err_dev;
|
||
|
}
|
||
|
|
||
|
atomic_set(&fmp->fips_start, 0);
|
||
|
|
||
|
ret = exynos_fmp_fips_register(fmp);
|
||
|
if (ret) {
|
||
|
dev_err(fmp->dev, "%s: Fail to exynos_fmp_fips_register. ret(0x%x)",
|
||
|
__func__, ret);
|
||
|
goto err_dev;
|
||
|
}
|
||
|
|
||
|
/* Check fmp status for featuring */
|
||
|
np = fmp->dev->of_node;
|
||
|
ret = of_property_read_u8(np, "dun-swap", &dun_swap);
|
||
|
if (ret) {
|
||
|
dev_info(fmp->dev, "read dun_swap failed = 0x%x, set to 0\n", __func__, ret);
|
||
|
dun_swap = 0;
|
||
|
}
|
||
|
fmp->dun_swap = dun_swap;
|
||
|
dev_info(fmp->dev, "Exynos FMP dun swap = %d\n", fmp->dun_swap);
|
||
|
fmp->fips_run = 0;
|
||
|
dev_info(fmp->dev, "Exynos FMP driver is initialized\n");
|
||
|
dev_info(fmp->dev, "Exynos FMP driver Version: %s\n", FMP_DRV_VERSION);
|
||
|
return fmp;
|
||
|
|
||
|
err_dev:
|
||
|
devm_kfree(&pdev->dev, fmp);
|
||
|
err:
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void exynos_fmp_exit(struct platform_device *pdev)
|
||
|
{
|
||
|
struct exynos_fmp *fmp = dev_get_drvdata(&pdev->dev);
|
||
|
|
||
|
exynos_fmp_fips_deregister(fmp);
|
||
|
devm_kfree(&pdev->dev, fmp);
|
||
|
}
|
||
|
|
||
|
static int exynos_fmp_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct exynos_fmp *fmp_ctx = exynos_fmp_init(pdev);
|
||
|
int ret;
|
||
|
|
||
|
if (!fmp_ctx) {
|
||
|
dev_err(&pdev->dev,
|
||
|
"%s: Fail to get fmp_ctx\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
fmp_dev = &pdev->dev;
|
||
|
dev_set_drvdata(&pdev->dev, fmp_ctx);
|
||
|
|
||
|
ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36));
|
||
|
if (ret) {
|
||
|
dev_warn(&pdev->dev,
|
||
|
"%s: No suitable DMA available (%d)\n", __func__, ret);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int exynos_fmp_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
exynos_fmp_exit(pdev);
|
||
|
|
||
|
dev_info(&pdev->dev, "%s: complete remove fmp driver\n", __func__);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id exynos_fmp_match[] = {
|
||
|
{ .compatible = "samsung,exynos-fmp" },
|
||
|
{},
|
||
|
};
|
||
|
|
||
|
static struct platform_driver exynos_fmp_driver = {
|
||
|
.driver = {
|
||
|
.name = "exynos-fmp",
|
||
|
.owner = THIS_MODULE,
|
||
|
.pm = NULL,
|
||
|
.of_match_table = exynos_fmp_match,
|
||
|
},
|
||
|
.probe = exynos_fmp_probe,
|
||
|
.remove = exynos_fmp_remove,
|
||
|
};
|
||
|
module_platform_driver(exynos_fmp_driver);
|
||
|
|
||
|
MODULE_DESCRIPTION("FMP driver");
|
||
|
MODULE_AUTHOR("Boojin Kim <boojin.kim@samsung.com>");
|
||
|
MODULE_LICENSE("GPL");
|