389 lines
10 KiB
C
Executable File
389 lines
10 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Exynos FMP crypt interface
|
|
*
|
|
* Copyright (C) 2018 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/slab.h>
|
|
#include <linux/bio.h>
|
|
#include <linux/crypto.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <crypto/fmp.h>
|
|
#include <crypto/diskcipher.h>
|
|
#include "fmp/fmp_fips_fops.h"
|
|
|
|
int exynos_fmp_crypt_clear(struct bio *bio, void *table_addr)
|
|
{
|
|
struct crypto_diskcipher *dtfm = crypto_diskcipher_get(bio);
|
|
struct fmp_crypto_info *ci;
|
|
struct fmp_request req;
|
|
int ret = 0;
|
|
|
|
if (unlikely(IS_ERR(dtfm))) {
|
|
pr_warn("%s: fails to get crypt\n", __func__);
|
|
return -EINVAL;
|
|
} else if (dtfm) {
|
|
#ifdef CONFIG_EXYNOS_FMP_FIPS
|
|
/* check fips flag. use fmp without diskcipher */
|
|
if (!dtfm->algo) {
|
|
req.table = table_addr;
|
|
ret = exynos_fmp_clear((void *)dtfm, &req);
|
|
if (ret) {
|
|
pr_warn("%s: fails to clear fips\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
ci = crypto_tfm_ctx(crypto_diskcipher_tfm(dtfm));
|
|
if (ci)
|
|
if (ci->enc_mode == EXYNOS_FMP_FILE_ENC) {
|
|
req.table = table_addr;
|
|
ret = crypto_diskcipher_clear_crypt(dtfm, &req);
|
|
}
|
|
}
|
|
if (ret)
|
|
pr_err("%s: fail to config desc (bio, tfm, ci) ret:%d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
int exynos_fmp_crypt_cfg(struct bio *bio, void *table_addr,
|
|
u32 page_idx, u32 sector_unit)
|
|
{
|
|
struct crypto_diskcipher *dtfm = crypto_diskcipher_get(bio);
|
|
u64 iv;
|
|
struct fmp_request req;
|
|
int ret = 0;
|
|
|
|
if (unlikely(IS_ERR(dtfm))) {
|
|
pr_warn("%s: fails to get crypt\n", __func__);
|
|
return -EINVAL;
|
|
} else if (dtfm) {
|
|
req.table = table_addr;
|
|
req.cmdq_enabled = 0;
|
|
req.iv = &iv;
|
|
req.ivsize = sizeof(iv);
|
|
#ifdef CONFIG_EXYNOS_FMP_FIPS
|
|
/* check fips flag. use fmp without diskcipher */
|
|
if (!dtfm->algo) {
|
|
if (exynos_fmp_crypt((void *)dtfm, &req))
|
|
pr_warn("%s: fails to test fips\n", __func__);
|
|
return 0;
|
|
}
|
|
#endif
|
|
iv = (dtfm->ivmode == IV_MODE_DUN) ? (bio_dun(bio) + page_idx) :
|
|
(bio->bi_iter.bi_sector + (sector_t)sector_unit);
|
|
ret = crypto_diskcipher_set_crypt(dtfm, &req);
|
|
if (ret)
|
|
pr_err("%s: fail to config desc (bio, tfm) ret:%d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
exynos_fmp_bypass(table_addr, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int fmp_crypt(struct crypto_diskcipher *tfm, void *priv)
|
|
{
|
|
struct fmp_crypto_info *ci = crypto_tfm_ctx(crypto_diskcipher_tfm(tfm));
|
|
|
|
return exynos_fmp_crypt(ci, priv);
|
|
}
|
|
|
|
static int fmp_clear(struct crypto_diskcipher *tfm, void *priv)
|
|
{
|
|
struct fmp_crypto_info *ci = crypto_tfm_ctx(crypto_diskcipher_tfm(tfm));
|
|
|
|
return exynos_fmp_clear(ci, priv);
|
|
}
|
|
|
|
static int fmp_setkey(struct crypto_diskcipher *tfm, const char *in_key,
|
|
u32 keylen, bool persistent)
|
|
{
|
|
struct fmp_crypto_info *ci = crypto_tfm_ctx(crypto_diskcipher_tfm(tfm));
|
|
|
|
return exynos_fmp_setkey(ci, (char *)in_key, keylen, persistent);
|
|
}
|
|
|
|
static int fmp_clearkey(struct crypto_diskcipher *tfm)
|
|
{
|
|
struct fmp_crypto_info *ci = crypto_tfm_ctx(crypto_diskcipher_tfm(tfm));
|
|
|
|
return exynos_fmp_clearkey(ci);
|
|
}
|
|
|
|
/* support crypto manager test without CRYPTO_MANAGER_DISABLE_TESTS */
|
|
static int fmp_do_test_crypt(struct crypto_diskcipher *tfm,
|
|
struct diskcipher_test_request *req)
|
|
{
|
|
if (!req) {
|
|
pr_err("%s: invalid parameter\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return exynos_fmp_test_crypt(crypto_tfm_ctx(crypto_diskcipher_tfm(tfm)),
|
|
req->iv, tfm->ivsize,
|
|
sg_virt(req->src), sg_virt(req->dst),
|
|
req->cryptlen, req->enc ? 1 : 0, tfm);
|
|
}
|
|
|
|
static inline void fmp_algo_init(struct crypto_tfm *tfm,
|
|
enum fmp_crypto_algo_mode algo)
|
|
{
|
|
struct fmp_crypto_info *ci = crypto_tfm_ctx(tfm);
|
|
struct crypto_diskcipher *diskc = __crypto_diskcipher_cast(tfm);
|
|
struct diskcipher_alg *alg = crypto_diskcipher_alg(diskc);
|
|
|
|
/* This field's stongly aligned 'fmp_crypto_info->use_diskc' */
|
|
diskc->algo = (u32)algo;
|
|
diskc->ivsize = FMP_IV_SIZE_16;
|
|
ci->ctx = dev_get_drvdata(alg->dev);
|
|
ci->algo_mode = algo;
|
|
}
|
|
|
|
static int fmp_aes_xts_init(struct crypto_tfm *tfm)
|
|
{
|
|
fmp_algo_init(tfm, EXYNOS_FMP_ALGO_MODE_AES_XTS);
|
|
return 0;
|
|
}
|
|
|
|
static int fmp_cbc_aes_init(struct crypto_tfm *tfm)
|
|
{
|
|
fmp_algo_init(tfm, EXYNOS_FMP_ALGO_MODE_AES_CBC);
|
|
return 0;
|
|
}
|
|
|
|
static struct diskcipher_alg fmp_algs[] = {{
|
|
.base = {
|
|
.cra_name = "xts(aes)-disk",
|
|
.cra_driver_name = "xts(aes)-disk(fmp)",
|
|
.cra_priority = 200,
|
|
.cra_module = THIS_MODULE,
|
|
.cra_ctxsize = sizeof(struct fmp_crypto_info),
|
|
.cra_init = fmp_aes_xts_init,
|
|
}
|
|
}, {
|
|
.base = {
|
|
.cra_name = "cbc(aes)-disk",
|
|
.cra_driver_name = "cbc(aes)-disk(fmp)",
|
|
.cra_priority = 200,
|
|
.cra_module = THIS_MODULE,
|
|
.cra_ctxsize = sizeof(struct fmp_crypto_info),
|
|
.cra_init = fmp_cbc_aes_init,
|
|
}
|
|
} };
|
|
|
|
#ifdef CONFIG_EXYNOS_FMP_FIPS
|
|
static const char pass[] = "passed";
|
|
static const char fail[] = "failed";
|
|
|
|
static ssize_t fmp_fips_result_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct exynos_fmp *fmp = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, sizeof(pass), "%s\n", fmp->result.overall ? pass : fail);
|
|
}
|
|
|
|
static ssize_t fmp_fips_aes_xts_result_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct exynos_fmp *fmp = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, sizeof(pass), "%s\n", fmp->result.aes_xts ? pass : fail);
|
|
}
|
|
|
|
static ssize_t fmp_fips_aes_cbc_result_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct exynos_fmp *fmp = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, sizeof(pass), "%s\n", fmp->result.aes_cbc ? pass : fail);
|
|
}
|
|
|
|
static ssize_t fmp_fips_sha256_result_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct exynos_fmp *fmp = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, sizeof(pass), "%s\n", fmp->result.sha256 ? pass : fail);
|
|
}
|
|
|
|
static ssize_t fmp_fips_hmac_result_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct exynos_fmp *fmp = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, sizeof(pass), "%s\n", fmp->result.hmac ? pass : fail);
|
|
}
|
|
|
|
static ssize_t fmp_fips_integrity_result_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct exynos_fmp *fmp = dev_get_drvdata(dev);
|
|
|
|
return snprintf(buf, sizeof(pass), "%s\n", fmp->result.integrity ? pass : fail);
|
|
}
|
|
|
|
static DEVICE_ATTR(fmp_fips_status, 0444, fmp_fips_result_show, NULL);
|
|
static DEVICE_ATTR(aes_xts_status, 0444, fmp_fips_aes_xts_result_show, NULL);
|
|
static DEVICE_ATTR(aes_cbc_status, 0444, fmp_fips_aes_cbc_result_show, NULL);
|
|
static DEVICE_ATTR(sha256_status, 0444, fmp_fips_sha256_result_show, NULL);
|
|
static DEVICE_ATTR(hmac_status, 0444, fmp_fips_hmac_result_show, NULL);
|
|
static DEVICE_ATTR(integrity_status, 0444, fmp_fips_integrity_result_show, NULL);
|
|
|
|
static struct attribute *fmp_fips_attr[] = {
|
|
&dev_attr_fmp_fips_status.attr,
|
|
&dev_attr_aes_xts_status.attr,
|
|
&dev_attr_aes_cbc_status.attr,
|
|
&dev_attr_sha256_status.attr,
|
|
&dev_attr_hmac_status.attr,
|
|
&dev_attr_integrity_status.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group fmp_fips_attr_group = {
|
|
.name = "fmp-fips",
|
|
.attrs = fmp_fips_attr,
|
|
};
|
|
|
|
static int __nocfi fmp_fips_fops_open(struct inode *inode, struct file *file)
|
|
{
|
|
return fmp_fips_open(inode, file);
|
|
}
|
|
|
|
static int __nocfi fmp_fips_fops_release(struct inode *inode, struct file *file)
|
|
{
|
|
return fmp_fips_release(inode, file);
|
|
}
|
|
|
|
static long __nocfi fmp_fips_fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg_)
|
|
{
|
|
return fmp_fips_ioctl(file, cmd, arg_);
|
|
}
|
|
|
|
static long __nocfi fmp_fips_fops_compat_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg_)
|
|
{
|
|
return fmp_fips_compat_ioctl(file, cmd, arg_);
|
|
}
|
|
|
|
static const struct file_operations fmp_fips_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = fmp_fips_fops_open,
|
|
.release = fmp_fips_fops_release,
|
|
.unlocked_ioctl = fmp_fips_fops_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = fmp_fips_fops_compat_ioctl,
|
|
#endif
|
|
};
|
|
#endif
|
|
|
|
static int exynos_fmp_probe(struct platform_device *pdev)
|
|
{
|
|
struct diskcipher_alg *alg;
|
|
struct exynos_fmp *fmp_ctx = exynos_fmp_init(pdev);
|
|
int ret;
|
|
int i;
|
|
|
|
if (!fmp_ctx) {
|
|
dev_err(&pdev->dev,
|
|
"%s: Fail to register diskciphero\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
dev_set_drvdata(&pdev->dev, fmp_ctx);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(fmp_algs); i++) {
|
|
alg = &fmp_algs[i];
|
|
alg->dev = &pdev->dev;
|
|
alg->init = NULL;
|
|
alg->setkey = fmp_setkey;
|
|
alg->clearkey = fmp_clearkey;
|
|
alg->crypt = fmp_crypt;
|
|
alg->clear = fmp_clear;
|
|
alg->do_crypt = fmp_do_test_crypt;
|
|
}
|
|
ret = crypto_register_diskciphers(fmp_algs, ARRAY_SIZE(fmp_algs));
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"%s: Fail to register diskcipher. ret = %d\n",
|
|
__func__, ret);
|
|
exynos_fmp_exit(pdev);
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifdef CONFIG_EXYNOS_FMP_FIPS
|
|
/* register FIPS ops */
|
|
ret = sysfs_create_group(&pdev->dev.kobj, &fmp_fips_attr_group);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "%s: Fail to create sysfs. ret(%d)\n",
|
|
__func__, ret);
|
|
crypto_unregister_diskciphers(fmp_algs, ARRAY_SIZE(fmp_algs));
|
|
exynos_fmp_exit(pdev);
|
|
return -EINVAL;
|
|
}
|
|
|
|
fmp_ctx->miscdev.fops = &fmp_fips_fops;
|
|
#endif
|
|
|
|
dev_info(&pdev->dev, "Exynos FMP driver is registered to diskcipher\n");
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_fmp_remove(struct platform_device *pdev)
|
|
{
|
|
void *drv_data = dev_get_drvdata(&pdev->dev);
|
|
|
|
if (!drv_data) {
|
|
pr_err("%s: Fail to get drvdata\n", __func__);
|
|
return 0;
|
|
}
|
|
crypto_unregister_diskciphers(fmp_algs, ARRAY_SIZE(fmp_algs));
|
|
#ifdef CONFIG_EXYNOS_FMP_FIPS
|
|
sysfs_remove_group(&pdev->dev.kobj, &fmp_fips_attr_group);
|
|
#endif
|
|
exynos_fmp_exit(drv_data);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id exynos_fmp_match[] = {
|
|
{ .compatible = "samsung,exynos-fmp" },
|
|
{},
|
|
};
|
|
|
|
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 crypto algo driver");
|