509 lines
11 KiB
C
509 lines
11 KiB
C
|
/*
|
||
|
* Copyright (C) 2016 Samsung Electronics Co., Ltd.
|
||
|
*
|
||
|
* 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/module.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/semaphore.h>
|
||
|
#include <linux/blkdev.h>
|
||
|
#include <linux/mmc/core.h>
|
||
|
#include <linux/mmc/host.h>
|
||
|
#include <linux/mmc/dw_mmc.h>
|
||
|
#include <linux/mmc/mmc.h>
|
||
|
|
||
|
#include "dw_mmc.h"
|
||
|
#include "dw_mmc-exynos.h"
|
||
|
#include "../card/queue.h"
|
||
|
|
||
|
static int check_data_equal(void *data1, void *data2)
|
||
|
{
|
||
|
return data1 == data2;
|
||
|
}
|
||
|
|
||
|
static int is_valid_bio_data(struct bio *bio)
|
||
|
{
|
||
|
if (bio->private_enc_mode < 0 ||
|
||
|
bio->private_enc_mode > EXYNOS_FMP_FILE_ENC)
|
||
|
return false;
|
||
|
|
||
|
if (bio->private_algo_mode < 0 ||
|
||
|
bio->private_algo_mode > EXYNOS_FMP_ALGO_MODE_AES_XTS)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static struct bio *is_get_bio(struct mmc_data *data, bool cmdq_enabled)
|
||
|
{
|
||
|
struct bio *bio;
|
||
|
|
||
|
if (!data) {
|
||
|
pr_err("%s: Invalid MMC data\n", __func__);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (cmdq_enabled) {
|
||
|
struct mmc_cmdq_req *cmdq_req;
|
||
|
struct mmc_request *mrq;
|
||
|
|
||
|
cmdq_req = container_of(data, struct mmc_cmdq_req, data);
|
||
|
mrq = &cmdq_req->mrq;
|
||
|
|
||
|
if (!mrq || !virt_addr_valid(mrq))
|
||
|
return NULL;
|
||
|
|
||
|
if (!mrq->req || !virt_addr_valid(mrq->req))
|
||
|
return NULL;
|
||
|
|
||
|
if (!mrq->req->bio || !virt_addr_valid(mrq->req->bio))
|
||
|
return NULL;
|
||
|
|
||
|
bio = mrq->req->bio;
|
||
|
} else {
|
||
|
struct mmc_queue_req *mq_rq;
|
||
|
struct mmc_blk_request *brq;
|
||
|
struct mmc_queue *mmcq;
|
||
|
|
||
|
brq = container_of(data, struct mmc_blk_request, data);
|
||
|
|
||
|
mq_rq = container_of(brq, struct mmc_queue_req, brq);
|
||
|
mmcq = container_of(mq_rq, struct mmc_queue, mqrq[2]);
|
||
|
|
||
|
if (!mmcq->mqrq_cur || !virt_addr_valid(mmcq->mqrq_cur))
|
||
|
return NULL;
|
||
|
|
||
|
if (!mmcq->mqrq_cur->req || !virt_addr_valid(mmcq->mqrq_cur->req))
|
||
|
return NULL;
|
||
|
|
||
|
if (!mmcq->mqrq_cur->req->bio || !virt_addr_valid(mmcq->mqrq_cur->req->bio))
|
||
|
return NULL;
|
||
|
|
||
|
bio = mmcq->mqrq_cur->req->bio;
|
||
|
}
|
||
|
|
||
|
return bio;
|
||
|
}
|
||
|
|
||
|
static int is_mmc_fmp_test_enabled(struct mmc_data *mmc_data,
|
||
|
struct platform_device *pdev,
|
||
|
bool cmdq_enabled)
|
||
|
{
|
||
|
struct bio *bio;
|
||
|
struct exynos_fmp *fmp = dev_get_drvdata(&pdev->dev);
|
||
|
|
||
|
if (!fmp)
|
||
|
return FALSE;
|
||
|
|
||
|
bio = is_get_bio(mmc_data, cmdq_enabled);
|
||
|
if (!bio) {
|
||
|
fmp->test_mode = 0;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
if (check_data_equal((void *)bio->bi_private, (void *)fmp->test_bh)
|
||
|
&& (uint64_t)fmp->test_bh) {
|
||
|
fmp->test_mode = 1;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
fmp->test_mode = 0;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static int exynos_mmc_fmp_key_size_cfg(struct fmp_crypto_setting *crypto,
|
||
|
uint32_t key_size)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
uint32_t size;
|
||
|
|
||
|
if (!crypto || !key_size) {
|
||
|
pr_err("%s: Invalid fmp data or size.\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
size = key_size;
|
||
|
if (crypto->algo_mode == EXYNOS_FMP_ALGO_MODE_AES_XTS)
|
||
|
size = size >> 1;
|
||
|
|
||
|
switch (size) {
|
||
|
case FMP_KEY_SIZE_16:
|
||
|
crypto->key_size = EXYNOS_FMP_KEY_SIZE_16;
|
||
|
break;
|
||
|
case FMP_KEY_SIZE_32:
|
||
|
crypto->key_size = EXYNOS_FMP_KEY_SIZE_32;
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("%s: FMP doesn't support key size %d\n", __func__, size);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int exynos_mmc_fmp_iv_cfg(struct fmp_crypto_setting *crypto,
|
||
|
sector_t sector,
|
||
|
int sector_offset,
|
||
|
pgoff_t page_index)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!crypto) {
|
||
|
pr_err("%s: Invalid fmp data\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
crypto->index = (uint32_t)page_index;
|
||
|
crypto->sector = sector + sector_offset;
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int exynos_mmc_fmp_key_cfg(struct fmp_crypto_setting *crypto,
|
||
|
unsigned char *key, unsigned long key_length)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!crypto) {
|
||
|
pr_err("%s: Invalid fmp data\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto out;
|
||
|
}
|
||
|
memset(crypto->key, 0, FMP_MAX_KEY_SIZE);
|
||
|
memcpy(crypto->key, key, key_length);
|
||
|
out:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int exynos_mmc_fmp_disk_cfg(struct mmc_data *mmc_data,
|
||
|
struct fmp_crypto_setting *crypto,
|
||
|
int sector_offset, bool cmdq_enabled)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct bio *bio;
|
||
|
|
||
|
if (!crypto) {
|
||
|
pr_err("%s: Invalid fmp data\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
memset(crypto, 0, sizeof(struct fmp_crypto_setting));
|
||
|
|
||
|
crypto->enc_mode = EXYNOS_FMP_DISK_ENC;
|
||
|
|
||
|
bio = is_get_bio(mmc_data, cmdq_enabled);
|
||
|
if (!bio)
|
||
|
goto bypass_out;
|
||
|
|
||
|
if ((bio->private_algo_mode == EXYNOS_FMP_BYPASS_MODE) ||
|
||
|
/* direct IO case */
|
||
|
(bio->private_enc_mode == EXYNOS_FMP_FILE_ENC))
|
||
|
goto bypass_out;
|
||
|
|
||
|
if (!is_valid_bio_data(bio))
|
||
|
goto bypass_out;
|
||
|
|
||
|
if (!bio->key) {
|
||
|
pr_err("%s: Invalid disk key\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
crypto->algo_mode = bio->private_algo_mode;
|
||
|
ret = exynos_mmc_fmp_key_size_cfg(crypto, bio->key_length);
|
||
|
if (ret)
|
||
|
goto bypass_out;
|
||
|
|
||
|
ret = exynos_mmc_fmp_iv_cfg(crypto, bio->bi_iter.bi_sector,
|
||
|
sector_offset, 0);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to configure fmp iv. ret(%d)\n",
|
||
|
__func__, ret);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
err:
|
||
|
return ret;
|
||
|
bypass_out:
|
||
|
crypto->algo_mode = EXYNOS_FMP_BYPASS_MODE;
|
||
|
ret = 0;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int exynos_mmc_fmp_direct_io_cfg(struct mmc_data *mmc_data,
|
||
|
struct fmp_crypto_setting *crypto,
|
||
|
int sector_offset, bool cmdq_enabled)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct bio *bio;
|
||
|
|
||
|
if (!crypto) {
|
||
|
pr_err("%s: Invalid fmp data\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
memset(crypto, 0, sizeof(struct fmp_crypto_setting));
|
||
|
|
||
|
crypto->enc_mode = EXYNOS_FMP_FILE_ENC;
|
||
|
|
||
|
bio = is_get_bio(mmc_data, cmdq_enabled);
|
||
|
if (!bio || (bio->private_algo_mode == EXYNOS_FMP_BYPASS_MODE))
|
||
|
goto bypass_out;
|
||
|
|
||
|
if (!is_valid_bio_data(bio))
|
||
|
goto bypass_out;
|
||
|
|
||
|
if (!bio->key) {
|
||
|
pr_err("%s: Invalid disk key\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
crypto->algo_mode = bio->private_algo_mode;
|
||
|
ret = exynos_mmc_fmp_key_size_cfg(crypto, bio->key_length);
|
||
|
if (ret)
|
||
|
goto bypass_out;
|
||
|
|
||
|
ret = exynos_mmc_fmp_iv_cfg(crypto, bio->bi_iter.bi_sector,
|
||
|
sector_offset, 0);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to configure fmp iv. ret(%d)\n",
|
||
|
__func__, ret);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
ret = exynos_mmc_fmp_key_cfg(crypto, bio->key, bio->key_length);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to configure fmp key. ret(%d)\n",
|
||
|
__func__, ret);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
err:
|
||
|
return ret;
|
||
|
bypass_out:
|
||
|
crypto->algo_mode = EXYNOS_FMP_BYPASS_MODE;
|
||
|
ret = 0;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int exynos_mmc_fmp_file_cfg(struct mmc_data *mmc_data,
|
||
|
struct page *page,
|
||
|
struct fmp_crypto_setting *crypto,
|
||
|
int sector_offset, bool cmdq_enabled)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct bio *bio;
|
||
|
pgoff_t page_index;
|
||
|
|
||
|
if (!crypto) {
|
||
|
pr_err("%s: Invalid fmp data\n", __func__);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
memset(crypto, 0, sizeof(struct fmp_crypto_setting));
|
||
|
|
||
|
crypto->enc_mode = EXYNOS_FMP_FILE_ENC;
|
||
|
|
||
|
if (!page || PageAnon(page))
|
||
|
goto bypass_out;
|
||
|
|
||
|
if (!page->mapping || !virt_addr_valid(page->mapping))
|
||
|
goto bypass_out;
|
||
|
|
||
|
if (page->mapping->private_algo_mode == EXYNOS_FMP_BYPASS_MODE)
|
||
|
goto bypass_out;
|
||
|
|
||
|
bio = is_get_bio(mmc_data, cmdq_enabled);
|
||
|
if (!bio)
|
||
|
goto bypass_out;
|
||
|
|
||
|
crypto->algo_mode = page->mapping->private_algo_mode;
|
||
|
ret = exynos_mmc_fmp_key_size_cfg(crypto, page->mapping->key_length);
|
||
|
if (ret)
|
||
|
goto bypass_out;
|
||
|
|
||
|
page_index = page->index - page->mapping->sensitive_data_index;
|
||
|
ret = exynos_mmc_fmp_iv_cfg(crypto, bio->bi_iter.bi_sector,
|
||
|
sector_offset, page_index);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to configure fmp iv. ret(%d)\n",
|
||
|
__func__, ret);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
ret = exynos_mmc_fmp_key_cfg(crypto, page->mapping->key,
|
||
|
page->mapping->key_length);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to configure fmp key. ret(%d)\n",
|
||
|
__func__, ret);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
err:
|
||
|
return ret;
|
||
|
bypass_out:
|
||
|
crypto->algo_mode = EXYNOS_FMP_BYPASS_MODE;
|
||
|
ret = 0;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int exynos_mmc_fmp_host_set_device(struct platform_device *host_pdev,
|
||
|
struct platform_device *pdev,
|
||
|
struct exynos_fmp_variant_ops *fmp_vops)
|
||
|
{
|
||
|
struct dw_mci *host;
|
||
|
struct dw_mci_exynos_priv_data *priv;
|
||
|
|
||
|
if (!host_pdev || !pdev || !fmp_vops) {
|
||
|
pr_err("%s: Fail to set device for fmp host\n", __func__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
host = dev_get_drvdata(&host_pdev->dev);
|
||
|
if (!host) {
|
||
|
pr_err("%s: Invalid Host device\n", __func__);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
priv = host->priv;
|
||
|
if (!priv) {
|
||
|
pr_err("%s: Invalid Host device private data\n", __func__);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
priv->fmp.pdev = pdev;
|
||
|
priv->fmp.vops = fmp_vops;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(exynos_mmc_fmp_host_set_device);
|
||
|
|
||
|
static inline void exynos_mmc_fmp_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);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int exynos_mmc_fmp_cfg(struct dw_mci *host,
|
||
|
void *desc,
|
||
|
struct mmc_data *mmc_data,
|
||
|
struct page *page,
|
||
|
int sector_offset,
|
||
|
bool cmdq_enabled)
|
||
|
{
|
||
|
int ret;
|
||
|
struct fmp_data_setting data;
|
||
|
struct dw_mci_exynos_priv_data *priv;
|
||
|
|
||
|
if (!host) {
|
||
|
pr_err("%s: Invalid Host device\n", __func__);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
priv = host->priv;
|
||
|
if (!priv || !priv->fmp.pdev) {
|
||
|
exynos_mmc_fmp_bypass(desc, cmdq_enabled);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
ret = is_mmc_fmp_test_enabled(mmc_data, priv->fmp.pdev, cmdq_enabled);
|
||
|
if (ret == TRUE)
|
||
|
goto out;
|
||
|
|
||
|
ret = exynos_mmc_fmp_disk_cfg(mmc_data, &data.disk, sector_offset, cmdq_enabled);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to configure FMP Disk Encryption. ret(%d)\n",
|
||
|
__func__, ret);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (data.disk.algo_mode != EXYNOS_FMP_BYPASS_MODE)
|
||
|
goto file_cfg;
|
||
|
|
||
|
ret = exynos_mmc_fmp_direct_io_cfg(mmc_data, &data.file, sector_offset,
|
||
|
cmdq_enabled);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to configure FMP direct IO File Encryption. ret(%d)\n",
|
||
|
__func__, ret);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (data.file.algo_mode != EXYNOS_FMP_BYPASS_MODE)
|
||
|
goto out;
|
||
|
|
||
|
file_cfg:
|
||
|
ret = exynos_mmc_fmp_file_cfg(mmc_data, page, &data.file, sector_offset,
|
||
|
cmdq_enabled);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to configure FMP File Encryption. ret(%d)\n",
|
||
|
__func__, ret);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
data.table = (struct fmp_table_setting *)desc;
|
||
|
data.cmdq_enabled = cmdq_enabled;
|
||
|
return priv->fmp.vops->config(priv->fmp.pdev, &data);
|
||
|
}
|
||
|
EXPORT_SYMBOL(exynos_mmc_fmp_cfg);
|
||
|
|
||
|
int exynos_mmc_fmp_clear(struct dw_mci *host, void *desc, bool cmdq_enabled)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct dw_mci_exynos_priv_data *priv;
|
||
|
struct fmp_data_setting data;
|
||
|
|
||
|
if (!host) {
|
||
|
pr_err("%s: Invalid Host device\n", __func__);
|
||
|
ret = -ENODEV;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
priv = host->priv;
|
||
|
if (!priv || !priv->fmp.pdev) {
|
||
|
ret = 0;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
data.table = (struct fmp_table_setting *)desc;
|
||
|
if (cmdq_enabled) {
|
||
|
if (!GET_CMDQ_FAS(data.table))
|
||
|
return ret;
|
||
|
} else {
|
||
|
if (!GET_FAS(data.table))
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = priv->fmp.vops->clear(priv->fmp.pdev, &data);
|
||
|
if (ret) {
|
||
|
pr_err("%s: Fail to clear FMP desc (%d)\n",
|
||
|
__func__, ret);
|
||
|
ret = -EINVAL;
|
||
|
goto err;
|
||
|
}
|
||
|
err:
|
||
|
return ret;
|
||
|
}
|
||
|
|