[COMMON] mmc: fmp: Add fmp. smu, srpmb
authorJaeHun Jung <jh0801.jung@samsung.com>
Wed, 21 Mar 2018 06:33:46 +0000 (15:33 +0900)
committerJaehun Jung <jh0801.jung@samsung.com>
Wed, 20 Jun 2018 00:17:50 +0000 (09:17 +0900)
Change-Id: I62b23e4a5ffa310fe7c8ec4cb05c977f9c9b2358
Signed-off-by: JaeHun Jung <jh0801.jung@samsung.com>
drivers/mmc/host/dw_mmc-exynos-fmp.c [new file with mode: 0644]
drivers/mmc/host/dw_mmc-exynos-fmp.h [new file with mode: 0644]
drivers/mmc/host/dw_mmc-exynos-smu.c [new file with mode: 0644]
drivers/mmc/host/dw_mmc-exynos-smu.h [new file with mode: 0644]
drivers/mmc/host/dw_mmc-srpmb.c [new file with mode: 0644]
drivers/mmc/host/dw_mmc-srpmb.h [new file with mode: 0644]

diff --git a/drivers/mmc/host/dw_mmc-exynos-fmp.c b/drivers/mmc/host/dw_mmc-exynos-fmp.c
new file mode 100644 (file)
index 0000000..f9d93be
--- /dev/null
@@ -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 <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->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 (file)
index 0000000..1bc3c7e
--- /dev/null
@@ -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 (file)
index 0000000..b602a11
--- /dev/null
@@ -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 <linux/module.h>
+#include <linux/of.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/dw_mmc.h>
+#include <crypto/smu.h>
+
+#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 (file)
index 0000000..9a31975
--- /dev/null
@@ -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 (file)
index 0000000..782e779
--- /dev/null
@@ -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 <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/smc.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/ioctl.h>
+#include <linux/mmc/mmc.h>
+#include <linux/delay.h>
+#include <linux/wakelock.h>
+
+#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 <ycool.kwon@samsung.com>");
+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 (file)
index 0000000..772f65d
--- /dev/null
@@ -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