blob: 21f86310458516f5213ebaa914c1e9169c6ecddf [file] [log] [blame]
// 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");