blob: f6ac2128bf24f5f05e312b70c3121464402611f0 [file] [log] [blame]
/*
* Exynos FMP test 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/of.h>
#include <linux/of_platform.h>
#include <linux/miscdevice.h>
#include <linux/crypto.h>
#include <linux/buffer_head.h>
#include <linux/genhd.h>
#include <linux/delay.h>
#include <crypto/authenc.h>
#include <crypto/fmp.h>
#include "fmp_fips_main.h"
#include "fmp_fips_fops.h"
#include "fmp_fips_selftest.h"
#include "fmp_fips_integrity.h"
#define MAX_SCAN_PART (50)
#define MAX_RETRY_COUNT (0x100000)
static const char pass[] = "passed";
static const char fail[] = "failed";
#define FMP_FIPS_INIT_STATE 0
#define FMP_FIPS_PROGRESS_STATE 1
#define FMP_FIPS_ERR_STATE 2
#define FMP_FIPS_SUCCESS_STATE 3
static int fmp_fips_state = FMP_FIPS_INIT_STATE;
bool in_fmp_fips_err(void)
{
if (fmp_fips_state == FMP_FIPS_INIT_STATE ||
fmp_fips_state == FMP_FIPS_ERR_STATE)
return true;
return false;
}
static void set_fmp_fips_state(uint32_t val)
{
fmp_fips_state = val;
}
static dev_t find_devt_for_selftest(struct exynos_fmp *fmp,
struct fmp_fips_data *data)
{
int i, idx = 0;
uint32_t count = 0;
uint64_t size;
uint64_t size_list[MAX_SCAN_PART];
dev_t devt_list[MAX_SCAN_PART];
dev_t devt_scan, devt;
struct block_device *bdev;
struct device *dev = fmp->dev;
fmode_t fmode = FMODE_WRITE | FMODE_READ;
memset(size_list, 0, sizeof(size_list));
memset(devt_list, 0, sizeof(devt_list));
if (!data) {
dev_err(dev, "Invalid fmp test data\n");
return (dev_t)0;
}
do {
for (i = 1; i < MAX_SCAN_PART; i++) {
devt_scan = blk_lookup_devt(data->block_type, i);
bdev = blkdev_get_by_dev(devt_scan, fmode, NULL);
if (IS_ERR(bdev))
continue;
else {
size_list[idx] = (uint64_t)i_size_read(bdev->bd_inode);
devt_list[idx++] = devt_scan;
blkdev_put(bdev, fmode);
}
}
if (!idx) {
mdelay(100);
count++;
continue;
}
for (i = 0; i < idx; i++) {
if (i == 0) {
size = size_list[i];
devt = devt_list[i];
} else {
if (size < size_list[i])
devt = devt_list[i];
}
}
bdev = blkdev_get_by_dev(devt, fmode, NULL);
dev_info(dev, "Found partno %d for FMP test\n",
bdev->bd_part->partno);
blkdev_put(bdev, fmode);
return devt;
} while (count < MAX_RETRY_COUNT);
dev_err(dev, "Block device isn't initialized yet for FMP test\n");
return (dev_t)0;
}
static int exynos_fmp_host_get_type(struct device *dev,
struct fmp_fips_data *data)
{
int ret = 0;
struct device_node *node = dev->of_node;
const char *type;
ret = of_property_read_string_index(node, "exynos,block-type", 0, &type);
if (ret) {
pr_err("%s: Could not get block type\n", __func__);
ret = -EINVAL;
goto err;
}
strlcpy(data->block_type, type, FMP_BLOCK_TYPE_NAME_LEN);
err:
return ret;
}
static int exynos_fmp_get_fips_offset(struct device *dev,
struct fmp_fips_data *data)
{
int ret = 0;
struct device_node *node = dev->of_node;
uint32_t offset;
ret = of_property_read_u32(node, "exynos,fips-block_offset", &offset);
if (ret) {
pr_err("%s: Could not get fips test block offset\n", __func__);
ret = -EINVAL;
goto err;
}
data->test_block_offset = offset;
err:
return ret;
}
int do_fmp_fips_init(struct exynos_fmp *fmp)
{
int ret = 0;
struct fmp_fips_data *data;
struct device *dev;
struct inode *inode;
struct super_block *sb;
unsigned long blocksize;
unsigned char blocksize_bits;
sector_t self_test_block;
fmode_t fmode = FMODE_WRITE | FMODE_READ;
if (!fmp) {
pr_err("%s: Invalid exynos fmp struct\n", __func__);
return -ENODEV;
}
dev = fmp->dev;
data = kmalloc(sizeof(struct fmp_fips_data), GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto err;
}
ret = exynos_fmp_host_get_type(dev, data);
if (ret) {
dev_err(dev, "%s: Fail to get host type. ret(%d)", __func__, ret);
goto err;
}
data->devt = find_devt_for_selftest(fmp, data);
if (!data->devt) {
dev_err(dev, "%s: Fail to find devt for self test\n", __func__);
ret = -ENODEV;
goto err;
}
data->bdev = blkdev_get_by_dev(data->devt, fmode, NULL);
if (IS_ERR(data->bdev)) {
dev_err(dev, "%s: Fail to open block device\n", __func__);
ret = -ENODEV;
goto err;
}
ret = exynos_fmp_get_fips_offset(dev, data);
if (ret) {
dev_err(dev, "%s: Fail to get fips offset. ret(%d)\n", __func__, ret);
goto err;
}
inode = data->bdev->bd_inode;
sb = inode->i_sb;
blocksize = sb->s_blocksize;
blocksize_bits = sb->s_blocksize_bits;
self_test_block = (i_size_read(inode) - (blocksize * data->test_block_offset))
>> blocksize_bits;
data->sector = self_test_block;
fmp->fips_data = data;
return ret;
err:
kfree(data);
return ret;
}
void do_fmp_fips_exit(struct exynos_fmp *fmp)
{
struct fmp_fips_data *data = fmp->fips_data;
fmode_t fmode = FMODE_WRITE | FMODE_READ;
if (!data)
return;
if (data->bdev)
blkdev_put(data->bdev, fmode);
kfree(data);
}
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->fips_result ? pass : fail);
}
static DEVICE_ATTR(fmp_fips_status, 0444, fmp_fips_result_show, NULL);
static struct attribute *fmp_fips_attr[] = {
&dev_attr_fmp_fips_status.attr,
NULL,
};
static struct attribute_group fmp_fips_attr_group = {
.name = "fmp-fips",
.attrs = fmp_fips_attr,
};
static const struct file_operations fmp_fips_fops = {
.owner = THIS_MODULE,
.open = fmp_fips_open,
.release = fmp_fips_release,
.unlocked_ioctl = fmp_fips_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fmp_fips_compat_ioctl,
#endif
};
int exynos_fmp_fips_init(struct exynos_fmp *fmp)
{
int ret = 0;
if (!fmp || !fmp->dev) {
pr_err("%s: Invalid exynos fmp dev\n", __func__);
return -EINVAL;
}
set_fmp_fips_state(FMP_FIPS_INIT_STATE);
fmp->miscdev.minor = MISC_DYNAMIC_MINOR;
fmp->miscdev.name = "fmp";
fmp->miscdev.fops = &fmp_fips_fops;
ret = misc_register(&fmp->miscdev);
if (ret) {
dev_err(fmp->dev, "%s: Fail to register misc device. ret(%d)\n",
__func__, ret);
goto misc_err;
}
ret = sysfs_create_group(&fmp->dev->kobj, &fmp_fips_attr_group);
if (ret) {
dev_err(fmp->dev, "%s: Fail to create sysfs. ret(%d)\n",
__func__, ret);
goto sysfs_err;
}
set_fmp_fips_state(FMP_FIPS_PROGRESS_STATE);
ret = do_fmp_fips_init(fmp);
if (ret) {
dev_err(fmp->dev, "%s: Fail to initialize fips test. ret(%d)\n",
__func__, ret);
goto err;
}
ret = do_fmp_selftest(fmp);
if (ret) {
dev_err(fmp->dev, "%s: self-tests for FMP failed\n", __func__);
goto err;
} else {
dev_info(fmp->dev, "%s: self-tests for FMP passed\n", __func__);
}
ret = do_fmp_integrity_check();
if (ret) {
dev_err(fmp->dev, "%s: integrity check for FMP failed\n", __func__);
goto err;
} else {
dev_info(fmp->dev, "%s: integrity check for FMP passed\n", __func__);
}
set_fmp_fips_state(FMP_FIPS_SUCCESS_STATE);
fmp->fips_result = 1;
do_fmp_fips_exit(fmp);
return 0;
err:
#if defined(CONFIG_NODE_FOR_SELFTEST_FAIL)
set_fmp_fips_state(FMP_FIPS_ERR_STATE);
fmp->fips_result = 0;
do_fmp_fips_exit(fmp);
return 0;
#else
panic("%s: Panic due to FMP self test for FIPS KAT", __func__);
#endif
sysfs_err:
misc_err:
return -1;
}
void exynos_fmp_fips_exit(struct exynos_fmp *fmp)
{
sysfs_remove_group(&fmp->dev->kobj, &fmp_fips_attr_group);
misc_deregister(&fmp->miscdev);
}