blob: 1823dca1c52a27dac35a147fa9cdfc7d2cad5261 [file] [log] [blame]
/*
* Exynos FMP driver
*
* Copyright (C) 2015 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/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/smc.h>
#include <linux/crypto.h>
#include <crypto/fmp.h>
#include "fmp_lib.h"
#include "fmp_fips_main.h"
#include "fmp_dev.h"
#include "fmp_derive_iv.h"
#include "fmp_version.h"
static LIST_HEAD(fmp_devices);
#define EXYNOS_HOST_LABEL "exynos-host"
#define EXYNOS_HOST_TYPE_LABEL "exynos,host-type"
static inline int is_set_fmp_disk_key(struct exynos_fmp *fmp)
{
return (fmp->status_disk_key == KEY_SET) ? TRUE : FALSE;
}
static inline int is_stored_fmp_disk_key(struct exynos_fmp *fmp)
{
return (fmp->status_disk_key == KEY_STORED) ? TRUE : FALSE;
}
static int fmpdev_set_disk_cfg(struct exynos_fmp *fmp,
struct fmp_data_setting *data)
{
int ret = 0;
struct fmp_table_setting *table = data->table;
struct fmp_crypto_setting *crypto = &data->disk;
ret = fmplib_set_algo_mode(fmp, table, crypto->algo_mode,
crypto->enc_mode, data->cmdq_enabled);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP encryption mode\n",
__func__);
goto err;
}
if (crypto->algo_mode == EXYNOS_FMP_BYPASS_MODE)
return 0;
if (is_stored_fmp_disk_key(fmp)) {
exynos_smc(SMC_CMD_FMP_DISK_KEY_SET, 0, 0, 0);
fmp->status_disk_key = KEY_SET;
} else if (!is_set_fmp_disk_key(fmp)) {
dev_err(fmp->dev, "%s: Cannot configure FMP because disk key is clear\n",
__func__);
ret = -EINVAL;
goto err;
}
ret = fmplib_set_key_size(fmp, table, crypto->key_size,
crypto->enc_mode, data->cmdq_enabled);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP key size\n", __func__);
goto err;
}
ret = fmplib_derive_iv(fmp, data->mapping, crypto,
crypto->enc_mode);
if (ret) {
dev_err(fmp->dev, "%s: Fail to derive FMP IV\n", __func__);
goto err;
}
ret = fmplib_set_iv(fmp, table, crypto->iv, crypto->enc_mode);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP IV\n", __func__);
goto err;
}
err:
return ret;
}
static int fmpdev_set_file_cfg(struct exynos_fmp *fmp,
struct fmp_data_setting *data)
{
int ret = 0;
struct fmp_table_setting *table = data->table;
struct fmp_crypto_setting *crypto = &data->file;
ret = fmplib_set_algo_mode(fmp, table, crypto->algo_mode,
crypto->enc_mode, data->cmdq_enabled);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP encryption mode\n",
__func__);
goto err;
}
if (crypto->algo_mode == EXYNOS_FMP_BYPASS_MODE)
return 0;
ret = fmplib_set_key(fmp, table, crypto->key, crypto->algo_mode,
crypto->key_size, crypto->enc_mode);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP key\n", __func__);
goto err;
}
ret = fmplib_set_key_size(fmp, table, crypto->key_size,
crypto->enc_mode, data->cmdq_enabled);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP key size\n", __func__);
goto err;
}
ret = fmplib_derive_iv(fmp, data->mapping, crypto,
crypto->enc_mode);
if (ret) {
dev_err(fmp->dev, "%s: Fail to derive FMP IV\n", __func__);
goto err;
}
ret = fmplib_set_iv(fmp, table, crypto->iv, crypto->enc_mode);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP IV\n", __func__);
goto err;
}
err:
return ret;
}
static int fmpdev_set_test_cfg(struct exynos_fmp *fmp,
struct fmp_data_setting *data)
{
int ret = 0;
struct fmp_crypto_setting *crypto;
if (!fmp->fips_data) {
dev_err(fmp->dev, "%s: Invalid test data\n", __func__);
goto err;
}
crypto = fmp->fips_data->crypto;
if (!data || !data->table) {
dev_err(fmp->dev, "%s: Invalid data or table data\n", __func__);
goto err;
}
ret = fmplib_set_algo_mode(fmp, data->table, crypto->algo_mode,
EXYNOS_FMP_FILE_ENC, data->cmdq_enabled);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP encryption mode\n",
__func__);
goto err;
}
if (crypto->algo_mode == EXYNOS_FMP_BYPASS_MODE)
return 0;
ret = fmplib_clear(fmp, data->table);
if (ret) {
pr_err("%s: Fail to clear FMP table\n", __func__);
goto err;
}
ret = fmplib_set_key(fmp, data->table, crypto->key, crypto->algo_mode,
crypto->key_size, EXYNOS_FMP_FILE_ENC);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP key\n", __func__);
goto err;
}
ret = fmplib_set_key_size(fmp, data->table, crypto->key_size,
EXYNOS_FMP_FILE_ENC, data->cmdq_enabled);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP key size\n", __func__);
goto err;
}
ret = fmplib_set_iv(fmp, data->table, crypto->iv, EXYNOS_FMP_FILE_ENC);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP IV\n", __func__);
goto err;
}
err:
return ret;
}
static int exynos_fmp_config(struct platform_device *pdev,
struct fmp_data_setting *data)
{
int ret = 0;
struct exynos_fmp *fmp;
if (!pdev || !data) {
pr_err("%s: Invalid fmp pdev or data\n", __func__);
ret = -EINVAL;
goto err;
}
fmp = dev_get_drvdata(&pdev->dev);
if (!fmp) {
pr_err("%s: Invalid fmp driver platform data\n", __func__);
ret = -ENODEV;
goto err;
}
if (unlikely(in_fmp_fips_err())) {
dev_err(fmp->dev, "%s: Fail to work fmp config due to fips in error.\n",
__func__);
return -EPERM;
}
if (fmp->test_mode) {
ret = fmpdev_set_test_cfg(fmp, data);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP test configuration\n",
__func__);
ret = -EPERM;
goto err;
} else
goto err;
}
ret = fmpdev_set_disk_cfg(fmp, data);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP disk configuration\n",
__func__);
ret = -EPERM;
goto err;
}
ret = fmpdev_set_file_cfg(fmp, data);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP file configuration\n",
__func__);
ret = -EPERM;
goto err;
}
err:
return ret;
}
static int exynos_fmp_set_disk_key(struct platform_device *pdev,
struct fmp_data_setting *data)
{
int ret = 0;
struct device *dev = &pdev->dev;
struct exynos_fmp *fmp;
struct fmp_crypto_setting *crypto;
struct fmp_table_setting *table;
if (!data) {
pr_err("%s: Invalid fmp data\n", __func__);
ret = -EINVAL;
goto err;
}
table = data->table;
crypto = &data->disk;
fmp = dev_get_drvdata(dev);
if (!fmp) {
pr_err("%s: Invalid fmp driver platform data\n", __func__);
ret = -ENODEV;
goto err;
}
ret = fmplib_set_key(fmp, table, crypto->key, crypto->algo_mode,
crypto->key_size, EXYNOS_FMP_DISK_ENC);
if (ret) {
dev_err(fmp->dev, "%s: Fail to set FMP disk key\n", __func__);
goto err;
}
err:
return ret;
}
static int exynos_fmp_clear_disk_key(struct platform_device *pdev)
{
int ret = 0;
struct device *dev = &pdev->dev;
struct exynos_fmp *fmp;
fmp = dev_get_drvdata(dev);
if (!fmp) {
pr_err("%s: Invalid fmp driver platform data\n", __func__);
ret = -ENODEV;
goto err;
}
ret = fmplib_clear_disk_key(fmp);
if (ret) {
dev_err(fmp->dev, "%s: Fail to clear FMP disk key\n", __func__);
goto err;
}
err:
return ret;
}
static int exynos_fmp_clear(struct platform_device *pdev,
struct fmp_data_setting *data)
{
int ret = 0;
struct device *dev = &pdev->dev;
struct exynos_fmp *fmp;
struct fmp_table_setting *table;
if (!data) {
pr_err("%s: Invalid fmp data\n", __func__);
ret = -EINVAL;
goto err;
}
table = data->table;
fmp = dev_get_drvdata(dev);
if (!fmp) {
pr_err("%s: Invalid fmp driver platform data\n", __func__);
ret = -ENODEV;
goto err;
}
ret = fmplib_clear(fmp, table);
if (ret) {
dev_err(fmp->dev, "%s: Fail to clear FMP disk key\n", __func__);
goto err;
}
err:
return ret;
}
const struct exynos_fmp_variant_ops exynos_fmp_ops = {
.name = "exynos",
.config = exynos_fmp_config,
.set_disk_key = exynos_fmp_set_disk_key,
.clear_disk_key = exynos_fmp_clear_disk_key,
.clear = exynos_fmp_clear,
};
static struct of_device_id exynos_fmp_match[] = {
{
.compatible = "samsung,exynos-fmp",
.data = (void *)&exynos_fmp_ops,
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_fmp_match);
struct platform_device *exynos_fmp_get_pdevice(struct device_node *node)
{
struct platform_device *fmp_pdev = NULL;
struct exynos_fmp *fmp = NULL;
if (!node) {
pr_err("%s: Invalid node\n", __func__);
goto out;
}
if (!of_device_is_available(node)) {
pr_err("%s: Unavailable device\n", __func__);
goto out;
}
if (list_empty(&fmp_devices)) {
pr_err("%s: Invalie device list\n", __func__);
fmp_pdev = ERR_PTR(-EPROBE_DEFER);
goto out;
}
list_for_each_entry(fmp, &fmp_devices, list) {
if (fmp->dev->of_node == node) {
pr_info("%s: Found FMP device\n", __func__);
break;
}
}
fmp_pdev = to_platform_device(fmp->dev);
pr_info("%s: Matching platform device\n", __func__);
out:
return fmp_pdev;
}
EXPORT_SYMBOL(exynos_fmp_get_pdevice);
struct exynos_fmp_variant_ops *exynos_fmp_get_variant_ops(struct device_node *node)
{
if (node) {
const struct of_device_id *match;
match = of_match_node(exynos_fmp_match, node);
if (match)
return (struct exynos_fmp_variant_ops *)(match->data);
pr_err("%s, Error matching\n", __func__);
} else {
pr_err("%s, Invalid node\n", __func__);
}
return NULL;
}
EXPORT_SYMBOL(exynos_fmp_get_variant_ops);
static struct platform_device *exynos_fmp_host_get_pdevice(struct device *dev)
{
struct device_node *node;
struct platform_device *host_pdev = NULL;
node = of_parse_phandle(dev->of_node, EXYNOS_HOST_LABEL, 0);
if (!node) {
dev_err(dev, "%s: exynos-host property not specified\n",
__func__);
goto out;
}
host_pdev = of_find_device_by_node(node);
out:
return host_pdev;
}
static int exynos_fmp_get_host_type(struct platform_device *pdev,
struct exynos_fmp *fmp)
{
int ret = 0;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
const char *type;
ret = of_property_read_string_index(node, EXYNOS_HOST_TYPE_LABEL, 0, &type);
if (ret) {
dev_err(fmp->dev, "%s: Could not get FMP host type\n", __func__);
ret = -EINVAL;
goto err;
}
strlcpy(fmp->host_type, type, FMP_HOST_TYPE_NAME_LEN);
err:
return ret;
}
static int exynos_fmp_host_get_dev(struct platform_device *pdev)
{
int ret = 0;
struct exynos_fmp *fmp;
struct platform_device *host_pdev;
struct exynos_fmp_variant_ops *fmp_vops;
fmp = dev_get_drvdata(&pdev->dev);
if (!fmp || !fmp->dev) {
pr_err("%s: invalid fmp context or device\n", __func__);
ret = -EINVAL;
goto err;
}
host_pdev = exynos_fmp_host_get_pdevice(fmp->dev);
fmp_vops = exynos_fmp_get_variant_ops(fmp->dev->of_node);
if (host_pdev == ERR_PTR(-EPROBE_DEFER)) {
dev_err(fmp->dev, "%s: Host device not probed yet\n", __func__);
ret = -EPROBE_DEFER;
goto err;
}
if (!host_pdev || !fmp_vops) {
dev_err(fmp->dev, "%s: invalid platform device %p or vops %p\n",
__func__, host_pdev, fmp_vops);
ret = -ENODEV;
goto err;
}
ret = exynos_fmp_get_host_type(pdev, fmp);
if (ret) {
dev_err(fmp->dev, "%s: invalid Host type. ret(%d)\n", __func__, ret);
goto err;
}
fmp->host_pdev = host_pdev;
if (!strncmp(fmp->host_type, "ufs", sizeof("ufs")))
return exynos_ufs_fmp_host_set_device(host_pdev, pdev, fmp_vops);
else if (!strncmp(fmp->host_type, "mmc", sizeof("mmc")))
return exynos_mmc_fmp_host_set_device(host_pdev, pdev, fmp_vops);
dev_err(fmp->dev, "%s: Not matched Host type(%s)\n", __func__,
fmp->host_type);
ret = -EINVAL;
err:
return ret;
}
static int exynos_fmp_probe(struct platform_device *pdev)
{
int ret = 0;
struct exynos_fmp *fmp;
struct device *dev = &pdev->dev;
if (!pdev) {
pr_err("%s: Invalid platform_device.\n", __func__);
ret = -EINVAL;
goto err_pdev;
}
fmp = kzalloc(sizeof(struct exynos_fmp), GFP_KERNEL);
if (!fmp) {
ret = -ENOMEM;
goto err_mem;
}
fmp->dev = &pdev->dev;
if (!fmp->dev) {
pr_err("%s: Invalid device.\n", __func__);
ret = -EINVAL;
goto err_dev;
}
fmp->status_disk_key = KEY_CLEAR;
dev_set_drvdata(dev, fmp);
list_add_tail(&fmp->list, &fmp_devices);
ret = exynos_fmp_host_get_dev(pdev);
if (ret == -EPROBE_DEFER) {
dev_err(fmp->dev, "%s: Host device not proved yet. ret = %d\n",
__func__, ret);
goto err_dev;
} else if (ret) {
dev_err(fmp->dev, "%s: Fail to get Host device. ret = %d\n",
__func__, ret);
goto err_dev;
}
dev_info(fmp->dev, "Exynos FMP Version: %s\n", FMP_DRV_VERSION);
ret = exynos_fmp_fips_init(fmp);
if (ret) {
dev_err(fmp->dev, "%s: Fail to initialize fmp fips. ret(%d)",
__func__, ret);
exynos_fmp_fips_exit(fmp);
goto err_dev;
}
dev_info(fmp->dev, "%s: Exynos FMP driver is proved\n", __func__);
return ret;
err_dev:
kfree(fmp);
err_mem:
err_pdev:
return ret;
}
static int exynos_fmp_remove(struct platform_device *pdev)
{
struct exynos_fmp *fmp;
struct device *dev = &pdev->dev;
fmp = dev_get_drvdata(dev);
if (!fmp)
return 0;
exynos_fmp_fips_exit(fmp);
kfree(fmp);
return 0;
}
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,
};
static int __init fmp_init(void)
{
return platform_driver_register(&exynos_fmp_driver);
}
late_initcall(fmp_init);
static void __exit fmp_exit(void)
{
platform_driver_unregister(&exynos_fmp_driver);
}
module_exit(fmp_exit);
MODULE_DESCRIPTION("Exynos Spedific FMP(Flash Memory Protector) driver");