603 lines
13 KiB
C
603 lines
13 KiB
C
/*
|
|
* 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");
|