From: JaeHun Jung Date: Wed, 21 Mar 2018 06:33:46 +0000 (+0900) Subject: [COMMON] mmc: fmp: Add fmp. smu, srpmb X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=60032381b781a8f79c5b1aca7eb93c6f32dbfc46;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git [COMMON] mmc: fmp: Add fmp. smu, srpmb Change-Id: I62b23e4a5ffa310fe7c8ec4cb05c977f9c9b2358 Signed-off-by: JaeHun Jung --- diff --git a/drivers/mmc/host/dw_mmc-exynos-fmp.c b/drivers/mmc/host/dw_mmc-exynos-fmp.c new file mode 100644 index 000000000000..f9d93be13bd0 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-exynos-fmp.c @@ -0,0 +1,494 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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->fmp_ci.private_enc_mode < 0 || + bio->fmp_ci.private_enc_mode > EXYNOS_FMP_DISK_ENC) + return false; + + if (bio->fmp_ci.private_algo_mode < 0 || + bio->fmp_ci.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 = NULL; + + if (!data) { + pr_err("%s: Invalid MMC data\n", __func__); + return NULL; + } + + if (cmdq_enabled) { +#if 0 /* CMDQ is not implemented on the current kernel */ + struct mmc_cmdq_req *cmdq_req; + struct mmc_request *mrq; + + cmdq_req = container_of(data, struct mmc_cmdq_req, data); + if (!cmdq_req) + return NULL; + + mrq = &cmdq_req->mrq; + if (!mrq || !mrq->req || !mrq->req->bio) + return NULL; + + bio = mrq->req->bio; +#endif + } else { + struct mmc_queue_req *mq_rq; + struct mmc_blk_request *brq; + + brq = container_of(data, struct mmc_blk_request, data); + if (!brq) + return NULL; + + mq_rq = container_of(brq, struct mmc_queue_req, brq); + if (!mq_rq || !mq_rq->req || !mq_rq->req->bio) + return NULL; + bio = mq_rq->req->bio; + + if (!virt_addr_valid(bio)) + return NULL; + } + + 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 = 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->fmp_ci.private_algo_mode == EXYNOS_FMP_BYPASS_MODE) || + /* direct IO case */ + (bio->fmp_ci.private_enc_mode == EXYNOS_FMP_FILE_ENC)) + goto bypass_out; + + if (!is_valid_bio_data(bio)) + goto bypass_out; + + if (!bio->fmp_ci.key) { + pr_err("%s: Invalid disk key\n", __func__); + ret = -EINVAL; + goto err; + } + + crypto->algo_mode = bio->fmp_ci.private_algo_mode; + ret = exynos_mmc_fmp_key_size_cfg(crypto, bio->fmp_ci.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->fmp_ci.private_algo_mode == EXYNOS_FMP_BYPASS_MODE)) + goto bypass_out; + + if (!is_valid_bio_data(bio)) + goto bypass_out; + + crypto->algo_mode = bio->fmp_ci.private_algo_mode; + ret = exynos_mmc_fmp_key_size_cfg(crypto, bio->fmp_ci.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->fmp_ci.key, + bio->fmp_ci.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; + struct _fmp_ci *ci; + + 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 || !page->mapping || PageAnon(page)) + goto bypass_out; + + bio = is_get_bio(mmc_data, cmdq_enabled); + if (!bio || !virt_addr_valid(bio)) + goto bypass_out; + + ci = &page->mapping->fmp_ci; + if (ci->private_algo_mode == EXYNOS_FMP_BYPASS_MODE) + goto bypass_out; + + crypto->algo_mode = ci->private_algo_mode; + ret = exynos_mmc_fmp_key_size_cfg(crypto, ci->key_length); + if (ret) + goto bypass_out; + + 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, ci->key, ci->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); + } +} + +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_test; + + 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.mapping = page->mapping; +out_test: + 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; +} + diff --git a/drivers/mmc/host/dw_mmc-exynos-fmp.h b/drivers/mmc/host/dw_mmc-exynos-fmp.h new file mode 100644 index 000000000000..1bc3c7eeec6e --- /dev/null +++ b/drivers/mmc/host/dw_mmc-exynos-fmp.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#ifndef _MMC_EXYNOS_FMP_H_ +#define _MMC_EXYNOS_FMP_H_ + +#ifdef CONFIG_MMC_DW_EXYNOS_FMP +int exynos_mmc_fmp_cfg(struct dw_mci *host, + void *desc, + struct mmc_data *mmc_data, + struct page *page, + int sector_offfset, + bool cmdq_enabled); +int exynos_mmc_fmp_clear(struct dw_mci *host, void *desc, + bool cmdq_enabled); +#else +inline 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) +{ + struct dw_mci_exynos_priv_data *priv; + + if (host) { + priv = host->priv; + if (priv) { + priv->fmp.pdev = NULL; + priv->fmp.vops = NULL; + } + } + return 0; +} + +inline int exynos_mmc_fmp_clear(struct dw_mci *host, void *desc, + bool cmdq_enabled) +{ + return 0; +} +#endif /* CONFIG_MMC_DW_EXYNOS_FMP */ +#endif /* _MMC_EXYNOS_FMP_H_ */ diff --git a/drivers/mmc/host/dw_mmc-exynos-smu.c b/drivers/mmc/host/dw_mmc-exynos-smu.c new file mode 100644 index 000000000000..b602a11785e9 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-exynos-smu.c @@ -0,0 +1,157 @@ +/* + * 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 +#include +#include +#include +#include + +#include "dw_mmc.h" +#include "dw_mmc-exynos.h" + +#define EXYNOS_MMC_SMU_LABEL "mmc-exynos-smu" + +static void exynos_mmc_smu_entry0_init(struct dw_mci *host) +{ + mci_writel(host, MPSBEGIN0, 0); + mci_writel(host, MPSEND0, 0xffffffff); + mci_writel(host, MPSLUN0, 0xff); + mci_writel(host, MPSCTRL0, DWMCI_MPSCTRL_BYPASS); +} + +static struct exynos_smu_variant_ops *exynos_mmc_smu_get_vops(struct device *dev) +{ + struct exynos_smu_variant_ops *smu_vops = NULL; + struct device_node *node; + + node = of_parse_phandle(dev->of_node, EXYNOS_MMC_SMU_LABEL, 0); + if (!node) { + dev_err(dev, "%s: exynos-mmc-smu property not specified\n", + __func__); + goto err; + } + + smu_vops = exynos_smu_get_variant_ops(node); + if (!smu_vops) + dev_err(dev, "%s: Fail to get smu_vops\n", __func__); + + of_node_put(node); +err: + return smu_vops; +} + +static struct platform_device *exynos_mmc_smu_get_pdevice(struct device *dev) +{ + struct device_node *node; + struct platform_device *smu_pdev = NULL; + + node = of_parse_phandle(dev->of_node, EXYNOS_MMC_SMU_LABEL, 0); + if (!node) { + dev_err(dev, "%s: exynos-mmc-smu property not specified\n", + __func__); + goto err; + } + + smu_pdev = exynos_smu_get_pdevice(node); +err: + return smu_pdev; +} + +int exynos_mmc_smu_get_dev(struct dw_mci *host) +{ + int ret; + struct device *dev; + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (!host || !host->dev) { + pr_err("%s: invalid mmc host, host->dev\n", __func__); + ret = -EINVAL; + goto err; + } + + dev = host->dev; + priv->smu.vops = exynos_mmc_smu_get_vops(dev); + priv->smu.pdev = exynos_mmc_smu_get_pdevice(dev); + + if (priv->smu.pdev == ERR_PTR(-EPROBE_DEFER)) { + dev_err(dev, "%s: SMU device not probed yet\n", __func__); + ret = -EPROBE_DEFER; + goto err; + } + + if (!priv->smu.pdev || !priv->smu.vops) { + dev_err(dev, "%s: Host device doesn't have SMU or fail to get SMU", + __func__); + ret = -ENODEV; + goto err; + } + + return 0; +err: + priv->smu.pdev = NULL; + priv->smu.vops = NULL; + + return ret; +} + +int exynos_mmc_smu_init(struct dw_mci *host) +{ + struct smu_data_setting smu_set; + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (!priv->smu.vops || !priv->smu.pdev) { + exynos_mmc_smu_entry0_init(host); + return 0; + } + + smu_set.id = SMU_EMBEDDED; + smu_set.command = SMU_INIT; + return priv->smu.vops->init(priv->smu.pdev, &smu_set); +} + +int exynos_mmc_smu_sec_cfg(struct dw_mci *host) +{ + struct smu_data_setting smu_set; + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (!priv->smu.vops || !priv->smu.pdev) + return 0; + + smu_set.id = SMU_EMBEDDED; + smu_set.desc_type = CFG_DESCTYPE; + return priv->smu.vops->sec_config(priv->smu.pdev, &smu_set); +} + +int exynos_mmc_smu_resume(struct dw_mci *host) +{ + struct smu_data_setting smu_set; + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (!priv->smu.vops || !priv->smu.pdev) { + exynos_mmc_smu_entry0_init(host); + return 0; + } + + smu_set.id = SMU_EMBEDDED; + return priv->smu.vops->resume(priv->smu.pdev, &smu_set); +} + +int exynos_mmc_smu_abort(struct dw_mci *host) +{ + struct smu_data_setting smu_set; + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (!priv->smu.vops || !priv->smu.pdev) + return 0; + + smu_set.id = SMU_EMBEDDED; + smu_set.command = SMU_ABORT; + return priv->smu.vops->abort(priv->smu.pdev, &smu_set); +} diff --git a/drivers/mmc/host/dw_mmc-exynos-smu.h b/drivers/mmc/host/dw_mmc-exynos-smu.h new file mode 100644 index 000000000000..9a31975eea7c --- /dev/null +++ b/drivers/mmc/host/dw_mmc-exynos-smu.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#ifndef _MMC_EXYNOS_SMU_H_ +#define _MMC_EXYNOS_SMU_H_ + +#ifdef CONFIG_MMC_DW_EXYNOS_SMU +int exynos_mmc_smu_get_dev(struct dw_mci *host); +int exynos_mmc_smu_init(struct dw_mci *host); +int exynos_mmc_smu_sec_cfg(struct dw_mci *host); +int exynos_mmc_smu_resume(struct dw_mci *host); +int exynos_mmc_smu_abort(struct dw_mci *host); +#else +inline int exynos_mmc_smu_get_dev(struct dw_mci *host) +{ + struct dw_mci_exynos_priv_data *priv; + + if (!host || !host->priv) + goto out; + + priv = host->priv; + priv->smu.pdev = NULL; + priv->smu.vops = NULL; +out: + return 0; +} + +inline int exynos_mmc_smu_init(struct dw_mci *host) +{ +#if 0 + mci_writel(host, MPSBEGIN0, 0); + mci_writel(host, MPSEND0, 0xffffffff); + mci_writel(host, MPSLUN0, 0xff); + mci_writel(host, MPSCTRL0, DWMCI_MPSCTRL_BYPASS); +#endif + return 0; +} + +inline int exynos_mmc_smu_sec_cfg(struct dw_mci *host) +{ + return 0; +} + +inline int exynos_mmc_smu_resume(struct dw_mci *host) +{ +#if 0 + mci_writel(host, MPSBEGIN0, 0); + mci_writel(host, MPSEND0, 0xffffffff); + mci_writel(host, MPSLUN0, 0xff); + mci_writel(host, MPSCTRL0, DWMCI_MPSCTRL_BYPASS); +#endif + return 0; +} + +inline int exynos_mmc_smu_abort(struct dw_mci *host) +{ + return 0; +} +#endif /* CONFIG_MMC_DW_EXYNOS_SMU */ +#endif /* _MMC_EXYNOS_SMU_H_ */ diff --git a/drivers/mmc/host/dw_mmc-srpmb.c b/drivers/mmc/host/dw_mmc-srpmb.c new file mode 100644 index 000000000000..782e779373b8 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-srpmb.c @@ -0,0 +1,446 @@ +/* + * Secure RPMB Driver for Exynos MMC RPMB + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dw_mmc-srpmb.h" + +#define MMC_SRPMB_DEVICE_PROPNAME "samsung,mmc-srpmb" +#define MMC_BLOCK_NAME "/dev/block/mmcblk0rpmb" + +#if defined(DEBUG_SRPMB) +static void dump_packet(u8 *data, int len) +{ + u8 s[17]; + int i, j; + + s[16] = '\0'; + for (i = 0; i < len; i += 16) { + printk("%06x :", i); + for (j = 0; j < 16; j++) { + printk(" %02x", data[i+j]); + s[j] = (data[i+j] < ' ' ? '.' : (data[i+j] > '}' ? '.' : data[i+j])); + } + printk(" |%s|\n", s); + } + printk("\n"); +} +#endif + +static void swap_packet(u8 *p, u8 *d) +{ + int i; + + for (i = 0; i < RPMB_PACKET_SIZE; i++) + d[i] = p[RPMB_PACKET_SIZE - 1 - i]; +} + +static void mmc_cmd_init(struct mmc_ioc_cmd *icmd) +{ + icmd->is_acmd = false; + icmd->arg = 0; + icmd->flags = MMC_RSP_R1; + icmd->blksz = RPMB_PACKET_SIZE; + icmd->blocks = 1; + icmd->postsleep_min_us = 0; + icmd->postsleep_max_us = 0; + icmd->data_timeout_ns = 0; + icmd->cmd_timeout_ms = 0; +} + +static int mmc_rpmb_access(struct _mmc_rpmb_ctx *ctx, struct _mmc_rpmb_req *req) +{ + int ret; + struct device *dev = ctx->dev; + static struct block_device *bdev; + struct gendisk *disk; + static const struct block_device_operations *fops; + struct mmc_ioc_cmd icmd; + struct rpmb_packet packet; + u8 *result_buf = NULL; + + /* get block device for mmc rpmb */ + if (bdev == NULL) { + bdev = blkdev_get_by_path(MMC_BLOCK_NAME, + FMODE_READ|FMODE_WRITE, NULL); + if (!bdev) { + dev_err(dev, "Fail to get block device for mmc srpmb\n"); + return -EINVAL; + } + + disk = bdev->bd_disk; + fops = disk->fops; + if (!fops->srpmb_access) { + dev_err(dev, "No function pointer for srpmb access\n"); + return -ENOTTY; + } + } + + wake_lock(&ctx->wakelock); + + /* Initialize mmc ioc command */ + mmc_cmd_init(&icmd); + + switch (req->type) { + case GET_WRITE_COUNTER: + icmd.write_flag = true; + icmd.flags = MMC_RSP_R1; + icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; + icmd.data_ptr = (unsigned long)req->rpmb_data; + + ret = fops->srpmb_access(bdev, &icmd); + if (ret != 0) { + req->status_flag = WRITE_COUNTER_SECURITY_OUT_ERROR; + dev_err(dev, "Fail to execute for srpmb write counter \ + security out: %x\n", ret); + break; + } + + memset(req->rpmb_data, 0, RPMB_PACKET_SIZE); + icmd.write_flag = false; + icmd.flags = MMC_RSP_R1; + icmd.opcode = MMC_READ_MULTIPLE_BLOCK; + + ret = fops->srpmb_access(bdev, &icmd); + if (ret != 0) { + req->status_flag = WRITE_COUNTER_SECURITY_IN_ERROR; + dev_err(dev, "Fail to execute for srpmb write counter \ + security in: %x\n", ret); + break; + } + req->status_flag = PASS_STATUS; + break; + case WRITE_DATA: + icmd.write_flag = RELIABLE_WRITE_REQ_SET; + icmd.flags = MMC_RSP_R1; + icmd.blocks = req->data_len / RPMB_PACKET_SIZE; + icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; + icmd.data_ptr = (unsigned long)req->rpmb_data; + + /* program data packet */ + ret = fops->srpmb_access(bdev, &icmd); + if (ret != 0) { + req->status_flag = WRITE_DATA_SECURITY_OUT_ERROR; + dev_err(dev, "Fail to write block for program data: %x\n", ret); + break; + } + + result_buf = (u8 *)kzalloc(RPMB_PACKET_SIZE, GFP_KERNEL); + if (result_buf == NULL) { + dev_err(dev, "Memory allocation failed\n"); + ret = -1; + break; + } + icmd.write_flag = true; + icmd.blocks = 1; + icmd.data_ptr = (unsigned long)result_buf; + memset(&packet, 0, RPMB_PACKET_SIZE); + packet.request = RESULT_READ_REQ; + swap_packet((u8 *)&packet, result_buf); + + /* result read request */ + ret = fops->srpmb_access(bdev, &icmd); + if (ret != 0) { + req->status_flag = WRITE_DATA_SECURITY_OUT_ERROR; + dev_err(dev, "Fail to write block for result: %x\n", ret); + goto wout; + } + + memset(result_buf, 0, RPMB_PACKET_SIZE); + icmd.write_flag = false; + icmd.blocks = 1; + icmd.opcode = MMC_READ_MULTIPLE_BLOCK; + + /* read multiple block for response */ + ret = fops->srpmb_access(bdev, &icmd); + if (ret != 0) { + req->status_flag = WRITE_DATA_SECURITY_IN_ERROR; + dev_err(dev, "Fail to read block for response: %x\n", ret); + goto wout; + } + memcpy(req->rpmb_data, result_buf, RPMB_PACKET_SIZE); + req->status_flag = PASS_STATUS; +wout: + kfree(result_buf); + break; + case READ_DATA: + icmd.write_flag = true; + icmd.flags = MMC_RSP_R1; + icmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; + icmd.data_ptr = (unsigned long)req->rpmb_data; + + result_buf = (u8 *)kzalloc(RPMB_PACKET_SIZE, GFP_KERNEL); + if (result_buf == NULL) { + dev_err(dev, "Memory allocation failed\n"); + ret = -1; + break; + } + + /* At read data with MMC, block count has to '0' */ + swap_packet(req->rpmb_data, result_buf); + ((struct rpmb_packet *)(result_buf))->count = 0; + swap_packet(result_buf, req->rpmb_data); + + kfree(result_buf); + + /* read data packet */ + ret = fops->srpmb_access(bdev, &icmd); + if (ret != 0) { + req->status_flag = READ_DATA_SECURITY_OUT_ERROR; + dev_err(dev, "Fail to write block for read data: %x\n", ret); + break; + } + + memset(req->rpmb_data, 0, req->data_len); + icmd.write_flag = false; + icmd.opcode = MMC_READ_MULTIPLE_BLOCK; + icmd.blocks = req->data_len/RPMB_PACKET_SIZE; + + /* read multiple block for response */ + ret = fops->srpmb_access(bdev, &icmd); + if (ret != 0) { + req->status_flag = READ_DATA_SECURITY_IN_ERROR; + dev_err(dev, "Fail to read block for response: %x\n", ret); + break; + } + req->status_flag = PASS_STATUS; + break; + default: + dev_err(dev, "Fail to invalid command: %x\n", ret); + } + + wake_unlock(&ctx->wakelock); + + return 0; +} + +static void mmc_rpmb_worker(struct work_struct *work) +{ + int ret; + struct device *dev; + struct _mmc_rpmb_ctx *ctx; + struct _mmc_rpmb_req *req; + + if (!work) { + printk(KERN_ERR "Fail to get work for mmc rpmb\n"); + return; + } + + ctx = container_of(work, struct _mmc_rpmb_ctx, work); + if (!ctx) { + printk(KERN_ERR "Fail to get mmc rpmb context\n"); + return; + } + + dev = ctx->dev; + req = (struct _mmc_rpmb_req *)ctx->wsm_virtaddr; + if (!req) { + dev_err(dev, "Invalid wsm virtual address for rpmb\n"); + return; + } + + ret = mmc_rpmb_access(ctx, req); + if (ret) { + dev_err(dev, "Fail to access mmc rpmb\n"); + return; + } + + return; +} + +static irqreturn_t mmc_rpmb_interrupt(int intr, void *arg) +{ + struct _mmc_rpmb_ctx *ctx = (struct _mmc_rpmb_ctx *)arg; + + queue_work(ctx->srpmb_queue, &ctx->work); + + return IRQ_HANDLED; +} + +static int init_mmc_srpmb(struct platform_device *pdev, struct _mmc_rpmb_ctx *ctx) +{ + int ret; + struct irq_data *rpmb_irqd; + irq_hw_number_t hwirq; + struct device *dev = &pdev->dev; + struct resource *res; + + if (!ctx) { + dev_err(dev, "Invalid rpmb context address\n"); + return -ENOMEM; + } + + /* allocation for wsm(world shared memory) */ + ctx->wsm_virtaddr = dma_alloc_coherent(dev, + sizeof(struct _mmc_rpmb_req) + RPMB_BUF_MAX_SIZE, + &ctx->wsm_phyaddr, GFP_KERNEL); + if (!ctx->wsm_virtaddr || !ctx->wsm_phyaddr) { + dev_err(dev, "Fail to alloc for srpmb wsm (world shared memory)\n"); + goto alloc_wsm_fail; + } + + /* get mmc srpmb irq number */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "Fail to get IRQ resource\n"); + goto get_irq_fail; + } + + ctx->irq = res->start; + if (ctx->irq <= 0) { + dev_err(dev, "Fail to get irq number for mmc rpmb\n"); + goto get_irq_fail; + } + + /* Get irq_data from irq number */ + rpmb_irqd = irq_get_irq_data(ctx->irq); + if (!rpmb_irqd) { + dev_err(dev, "Fail to get irq_data from irq number\n"); + goto get_irq_fail; + } + + /* Get hwirq from irq_data */ + hwirq = irqd_to_hwirq(rpmb_irqd); + if (hwirq < 0) { + dev_err(dev, "Fail to get hwirq from irq data\n"); + goto get_irq_fail; + } + + /* Smc call to transfer wsm address to secure world */ + ret = exynos_smc(SMC_SRPMB_WSM, ctx->wsm_phyaddr, hwirq, 0); + if (ret) + dev_err(dev, "Fail to smc call to initial wsm buffer\n"); + + return ret; + +get_irq_fail: + dma_free_coherent(dev, RPMB_BUF_MAX_SIZE, ctx->wsm_virtaddr, + ctx->wsm_phyaddr); +alloc_wsm_fail: + kfree(ctx); + ret = -ENOMEM; + return ret; +} + +static int mmc_srpmb_probe(struct platform_device *pdev) +{ + int ret; + struct _mmc_rpmb_ctx *ctx; + struct device *dev = &pdev->dev; + + /* allocation for rpmb context */ + ctx = kzalloc(sizeof(struct _mmc_rpmb_ctx), GFP_KERNEL); + if (!ctx) { + dev_err(dev, "Fail to alloc for mmc rpmb context\n"); + return -1; + } + + /* initialize mmc rpmb context */ + ret = init_mmc_srpmb(pdev, ctx); + if (ret) { + dev_err(dev, "Fail to initialize mmc srpmb\n"); + goto ctx_kfree; + } + + /* request irq for mmc rpmb handler */ + ret = request_irq(ctx->irq, mmc_rpmb_interrupt, + IRQF_TRIGGER_RISING, pdev->name, ctx); + if (ret) { + dev_err(dev, "Fail to request irq handler for mmc srpmb\n"); + goto dma_free; + } + + ctx->dev = dev; + INIT_WORK(&ctx->work, mmc_rpmb_worker); + + /* initialize workqueue for mmc rpmb handler */ + ctx->srpmb_queue = alloc_workqueue("srpmb_wq", + WQ_MEM_RECLAIM | WQ_UNBOUND, 1); + if (!ctx->srpmb_queue) { + dev_err(dev, "Fail to alloc workqueue for mmc srpmb\n"); + goto dma_free; + } + + platform_set_drvdata(pdev, ctx); + wake_lock_init(&ctx->wakelock, WAKE_LOCK_SUSPEND, "srpmb"); + + return 0; + +dma_free: + dma_free_coherent(dev, RPMB_BUF_MAX_SIZE, ctx->wsm_virtaddr, + ctx->wsm_phyaddr); +ctx_kfree: + kfree(ctx); + return -1; +} + +static int mmc_srpmb_remove(struct platform_device *pdev) +{ + struct _mmc_rpmb_ctx *ctx = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + if (ctx->srpmb_queue) + destroy_workqueue(ctx->srpmb_queue); + + dma_free_coherent(dev, RPMB_BUF_MAX_SIZE, ctx->wsm_virtaddr, + ctx->wsm_phyaddr); + + wake_lock_destroy(&ctx->wakelock); + + kfree(ctx); + return 0; +} + +static const struct of_device_id of_match_table[] = { + { .compatible = MMC_SRPMB_DEVICE_PROPNAME }, + { } +}; + +static struct platform_driver mmc_srpmb_plat_driver = { + .probe = mmc_srpmb_probe, + .driver = { + .name = "exynos-mmc-srpmb", + .owner = THIS_MODULE, + .of_match_table = of_match_table, + }, + .remove = mmc_srpmb_remove, +}; + +static int __init mmc_srpmb_init(void) +{ + return platform_driver_register(&mmc_srpmb_plat_driver); +} + +static void __exit mmc_srpmb_exit(void) +{ + platform_driver_unregister(&mmc_srpmb_plat_driver); +} + +late_initcall(mmc_srpmb_init); +module_exit(mmc_srpmb_exit); + +MODULE_AUTHOR("Yongtaek Kwon "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MMC SRPMB driver"); diff --git a/drivers/mmc/host/dw_mmc-srpmb.h b/drivers/mmc/host/dw_mmc-srpmb.h new file mode 100644 index 000000000000..772f65d8d4be --- /dev/null +++ b/drivers/mmc/host/dw_mmc-srpmb.h @@ -0,0 +1,81 @@ +/* + * Secure RPMB header for Exynos MMC RPMB + * + * 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. + */ + +#ifndef _MMC_SRPMB_H +#define _MMC_SRPMB_H + +#define GET_WRITE_COUNTER 1 +#define WRITE_DATA 2 +#define READ_DATA 3 + +#define AUTHEN_KEY_PROGRAM_RES 0x0100 +#define AUTHEN_KEY_PROGRAM_REQ 0x0001 +#define RESULT_READ_REQ 0x0005 +#define RPMB_END_ADDRESS 0x4000 + +#define RPMB_PACKET_SIZE 512 + +#define WRITE_COUNTER_DATA_LEN_ERROR 0x601 +#define WRITE_COUNTER_SECURITY_OUT_ERROR 0x602 +#define WRITE_COUNTER_SECURITY_IN_ERROR 0x603 +#define WRITE_DATA_LEN_ERROR 0x604 +#define WRITE_DATA_SECURITY_OUT_ERROR 0x605 +#define WRITE_DATA_RESULT_SECURITY_OUT_ERROR 0x606 +#define WRITE_DATA_SECURITY_IN_ERROR 0x607 +#define READ_LEN_ERROR 0x608 +#define READ_DATA_SECURITY_OUT_ERROR 0x609 +#define READ_DATA_SECURITY_IN_ERROR 0x60A + +#define PASS_STATUS 0xBABA + +#define IS_INCLUDE_RPMB_DEVICE "0:0:0:1" + +#define ON 1 +#define OFF 0 + +#define RPMB_BUF_MAX_SIZE 32 * 1024 +#define RELIABLE_WRITE_REQ_SET (1 << 31) + +struct _mmc_rpmb_ctx { + struct device *dev; + int irq; + void *wsm_virtaddr; + dma_addr_t wsm_phyaddr; + struct workqueue_struct *srpmb_queue; + struct work_struct work; + struct block_device *bdev; + struct wake_lock wakelock; +}; + +struct _mmc_rpmb_req { + uint32_t cmd; + + volatile uint32_t status_flag; + uint32_t type; + uint32_t data_len; + uint32_t inlen; + uint32_t outlen; + uint8_t rpmb_data[0]; +}; + +struct rpmb_packet { + u16 request; + u16 result; + u16 count; + u16 address; + u32 write_counter; + u8 nonce[16]; + u8 data[256]; + u8 Key_MAC[32]; + u8 stuff[196]; +}; + +#endif