blob: 9a457817a295acc61187602e218e7ca989a90972 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/memblock.h>
#include <linux/random.h>
#include <linux/compat.h>
#include "defendkey.h"
#define DEFENDKEY_DEVICE_NAME "defendkey"
#define DEFENDKEY_CLASS_NAME "defendkey"
static unsigned long mem_size;
static struct defendkey_mem defendkey_rmem;
static struct aml_defendkey_type defendkey_type = {
.decrypt_dtb = 0,
.status = 0,
};
static int defendkey_open(struct inode *inode, struct file *file)
{
struct aml_defendkey_dev *devp;
devp = container_of(inode->i_cdev, struct aml_defendkey_dev, cdev);
file->private_data = devp;
return 0;
}
static int defendkey_release(struct inode *inode, struct file *file)
{
return 0;
}
static loff_t defendkey_llseek(struct file *filp, loff_t off, int whence)
{
return 0;
}
static long defendkey_unlocked_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
unsigned long ret = 0;
struct aml_defendkey_dev *defendkey_dev;
defendkey_dev = file->private_data;
switch (cmd) {
case CMD_SECURE_CHECK:
ret = aml_is_secure_set(defendkey_dev->reg_base);
break;
case CMD_DECRYPT_DTB:
if (arg == 1) {
defendkey_type.decrypt_dtb = E_DECRYPT_DTB;
} else if (arg == 0) {
defendkey_type.decrypt_dtb = E_UPGRADE_CHECK;
} else {
return -EINVAL;
pr_info("set defendkey type fail, invalid value\n");
}
break;
default:
return -EINVAL;
}
return ret;
}
#ifdef CONFIG_COMPAT
static long defendkey_compat_ioctl(struct file *filp,
unsigned int cmd, unsigned long args)
{
unsigned long ret;
args = (unsigned long)compat_ptr(args);
ret = defendkey_unlocked_ioctl(filp, cmd, args);
return ret;
}
#endif
static ssize_t defendkey_read(struct file *file,
char __user *buf, size_t count, loff_t *ppos)
{
ssize_t ret_value = RET_ERROR;
int ret = -EINVAL;
unsigned long mem_base_phy, check_offset;
void __iomem *mem_base_virt;
struct cpumask task_cpumask;
cpumask_copy(&task_cpumask, current->cpus_ptr);
set_cpus_allowed_ptr(current, cpumask_of(0));
if (defendkey_type.status == 1) {
defendkey_type.status = 0;
mem_base_phy = defendkey_rmem.base;
mem_base_virt = phys_to_virt(mem_base_phy);
check_offset = aml_sec_boot_check(AML_D_Q_IMG_SIG_HDR_SIZE,
mem_base_phy, mem_size, 0);
if (AML_D_Q_IMG_SIG_HDR_SIZE == (check_offset & 0xFFFF))
check_offset = (check_offset >> 16) & 0xFFFF;
else
check_offset = 0;
if (mem_size < count) {
pr_err("%s: data size overflow\n", __func__);
ret_value = RET_FAIL;
goto exit;
}
ret = copy_to_user((void __user *)buf,
(const void *)(mem_base_virt + check_offset),
count);
if (ret) {
ret_value = RET_FAIL;
pr_err("%s: copy_to_user fail! ret:%d\n",
__func__, ret);
goto exit;
}
} else {
ret_value = RET_ERROR;
pr_err("%s: need to decrypt dtb first\n", __func__);
goto exit;
}
ret_value = RET_SUCCESS;
pr_info("%s: read decrypted dtb OK\n", __func__);
exit:
set_cpus_allowed_ptr(current, &task_cpumask);
return ret_value;
}
static ssize_t defendkey_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
{
ssize_t ret_value = RET_ERROR;
int ret = -EINVAL;
unsigned long mem_base_phy, copy_base, copy_size;
void __iomem *mem_base_virt;
u64 option = 0, random = 0, option_random = 0;
int i;
struct cpumask task_cpumask;
cpumask_copy(&task_cpumask, current->cpus_ptr);
set_cpus_allowed_ptr(current, cpumask_of(0));
defendkey_type.status = 0;
mem_base_phy = defendkey_rmem.base;
mem_base_virt = phys_to_virt(mem_base_phy);
pr_info("defendkey: mem_base_phy:%lx mem_size:%lx mem_base_virt:%p count:%lx\n",
mem_base_phy, mem_size, mem_base_virt, (unsigned long)count);
random = get_random_long();
option_random = (random << 8);
option_random |= (random << 32);
for (i = 0; i <= count / mem_size; i++) {
copy_base = (unsigned long)buf + mem_size * i;
if (mem_size * (i + 1) > count)
copy_size = count - mem_size * i;
else
copy_size = mem_size;
ret = copy_from_user(mem_base_virt,
(const void __user *)copy_base, copy_size);
if (ret) {
pr_err("%s: copy_from_user fail!!\n", __func__);
ret = -EFAULT;
goto exit;
}
if (i == 0) {
if (count <= mem_size)
option = NSTATE_ALL | option_random;
else
option = NSTATE_INIT | option_random;
} else if (i == count / mem_size) {
if (count % mem_size != 0)
option = NSTATE_FINAL | option_random;
else
break;
} else {
option = NSTATE_UPDATE | option_random;
if (count % mem_size == 0 && i == count / mem_size - 1)
option = NSTATE_FINAL | option_random;
}
pr_info("defendkey: copy_size:0x%lx, option:0x%llx\n",
copy_size, option);
if (defendkey_type.decrypt_dtb == E_DECRYPT_DTB)
ret = aml_sec_boot_check(AML_D_P_IMG_DECRYPT,
mem_base_phy, copy_size, 0);
else
ret = aml_sec_boot_check(AML_D_P_UPGRADE_CHECK,
mem_base_phy,
copy_size, option);
if (ret) {
ret_value = RET_FAIL;
pr_err("%s: %s fail! ret %d\n", __func__,
defendkey_type.decrypt_dtb
? "decrypt dtb" : "upgrade check", ret);
goto exit;
}
}
if (defendkey_type.decrypt_dtb)
defendkey_type.status = 1;
ret_value = RET_SUCCESS;
pr_info("%s: %s OK\n", __func__,
defendkey_type.decrypt_dtb ? "decrypt dtb" : "upgrade check");
exit:
set_cpus_allowed_ptr(current, &task_cpumask);
return ret_value;
}
static ssize_t version_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "version:2.00\n");
}
static ssize_t secure_check_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
ssize_t n = 0;
int ret;
struct aml_defendkey_dev *defendkey_dev;
defendkey_dev = container_of(cla, struct aml_defendkey_dev, cls);
ret = aml_is_secure_set(defendkey_dev->reg_base);
if (ret < 0)
n = sprintf(buf, "fail");
else if (ret == 0)
n = sprintf(buf, "raw");
else
n = sprintf(buf, "encrypt");
return n;
}
static ssize_t secure_verify_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return 0;
}
static ssize_t decrypt_dtb_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", defendkey_type.decrypt_dtb);
}
static ssize_t decrypt_dtb_store(struct class *cla,
struct class_attribute *attr,
const char *buf, size_t count)
{
unsigned int len;
if (buf[count - 1] == '\n')
len = count - 1;
else
len = count;
if (!strncmp(buf, "1", len))
defendkey_type.decrypt_dtb = E_DECRYPT_DTB;
else if (!strncmp(buf, "0", len))
defendkey_type.decrypt_dtb = E_UPGRADE_CHECK;
else
pr_info("set defendkey type fail, invalid value\n");
return count;
}
static const struct file_operations defendkey_fops = {
.owner = THIS_MODULE,
.llseek = defendkey_llseek,
.open = defendkey_open,
.release = defendkey_release,
.read = defendkey_read,
.write = defendkey_write,
.unlocked_ioctl = defendkey_unlocked_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = defendkey_compat_ioctl,
#endif
};
static CLASS_ATTR_RO(version);
static CLASS_ATTR_RO(secure_check);
static CLASS_ATTR_RO(secure_verify);
static CLASS_ATTR_RW(decrypt_dtb);
static struct attribute *defendkey_calss_attrs[] = {
&class_attr_version.attr,
&class_attr_secure_check.attr,
&class_attr_secure_verify.attr,
&class_attr_decrypt_dtb.attr,
NULL,
};
ATTRIBUTE_GROUPS(defendkey_calss);
static int __init early_defendkey_para(char *buf)
{
int ret;
if (!buf)
return -EINVAL;
ret = sscanf(buf, "%lx,%lx",
&defendkey_rmem.base, &defendkey_rmem.size);
if (ret != 2) {
pr_err("invalid boot args \"defendkey\"\n");
return -EINVAL;
}
if (defendkey_rmem.base > DEFENDKEY_LIMIT_ADDR) {
pr_err("defendkey reserved memory base overflow\n");
return -EINVAL;
}
pr_info("%s, base:%lx, size:%lx\n",
__func__, defendkey_rmem.base, defendkey_rmem.size);
ret = memblock_reserve(defendkey_rmem.base,
PAGE_ALIGN(defendkey_rmem.size));
if (ret < 0) {
pr_info("reserve memblock %lx - %lx failed\n",
defendkey_rmem.base,
defendkey_rmem.base + PAGE_ALIGN(defendkey_rmem.size));
return -EINVAL;
}
return 0;
}
early_param("defendkey", early_defendkey_para);
static int defendkey_probe(struct platform_device *pdev)
{
int ret;
struct device *devp;
struct resource *reg_mem = NULL;
void __iomem *reg_base = NULL;
struct aml_defendkey_dev *defendkey_dev;
reg_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!IS_ERR_OR_NULL(reg_mem)) {
reg_base = devm_ioremap_resource(&pdev->dev, reg_mem);
if (IS_ERR(reg_base)) {
dev_err(&pdev->dev, "reg0: cannot obtain I/O memory region.\n");
return PTR_ERR(reg_base);
}
} else {
dev_err(&pdev->dev, "get IORESOURCE_MEM error.\n");
return PTR_ERR(reg_base);
}
defendkey_dev = devm_kzalloc(&pdev->dev,
sizeof(*defendkey_dev), GFP_KERNEL);
if (!defendkey_dev) {
ret = -ENOMEM;
dev_err(&pdev->dev, "failed to allocate mem for defendkey_dev\n");
goto out;
}
defendkey_dev->pdev = pdev;
platform_set_drvdata(pdev, defendkey_dev);
ret = of_property_read_u32(pdev->dev.of_node,
"mem_size", (unsigned int *)&mem_size);
if (ret) {
dev_err(&pdev->dev, "failed to get mem_size\n");
goto error1;
}
if (mem_size > defendkey_rmem.size) {
ret = -EINVAL;
dev_err(&pdev->dev, "reserved memory is not enough\n");
goto error1;
}
ret = alloc_chrdev_region(&defendkey_dev->devno,
0, 1, DEFENDKEY_DEVICE_NAME);
if (ret < 0) {
dev_err(&pdev->dev, "failed to allocate major number\n");
goto error1;
}
defendkey_dev->reg_base = reg_base;
defendkey_dev->cls.name = DEFENDKEY_CLASS_NAME;
defendkey_dev->cls.owner = THIS_MODULE;
defendkey_dev->cls.class_groups = defendkey_calss_groups;
ret = class_register(&defendkey_dev->cls);
if (ret) {
dev_err(&pdev->dev, "failed to register class\n");
goto error2;
}
cdev_init(&defendkey_dev->cdev, &defendkey_fops);
defendkey_dev->cdev.owner = THIS_MODULE;
ret = cdev_add(&defendkey_dev->cdev, defendkey_dev->devno, 1);
if (ret) {
dev_err(&pdev->dev, "failed to add device\n");
goto error3;
}
devp = device_create(&defendkey_dev->cls, NULL,
defendkey_dev->devno, NULL, DEFENDKEY_DEVICE_NAME);
if (IS_ERR(devp)) {
dev_err(&pdev->dev, "failed to create device node\n");
ret = PTR_ERR(devp);
goto error4;
}
dev_info(&pdev->dev, "device %s created OK\n", DEFENDKEY_DEVICE_NAME);
return 0;
error4:
cdev_del(&defendkey_dev->cdev);
error3:
class_unregister(&defendkey_dev->cls);
error2:
unregister_chrdev_region(defendkey_dev->devno, 1);
error1:
devm_kfree(&pdev->dev, defendkey_dev);
out:
return ret;
}
static int defendkey_remove(struct platform_device *pdev)
{
struct aml_defendkey_dev *defendkey_dev = platform_get_drvdata(pdev);
unregister_chrdev_region(defendkey_dev->devno, 1);
device_destroy(&defendkey_dev->cls, defendkey_dev->devno);
cdev_del(&defendkey_dev->cdev);
class_unregister(&defendkey_dev->cls);
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct of_device_id defendkey_dt_match[] = {
{ .compatible = "amlogic, defendkey",
},
{},
};
static struct platform_driver defendkey_driver = {
.probe = defendkey_probe,
.remove = defendkey_remove,
.driver = {
.name = DEFENDKEY_DEVICE_NAME,
.owner = THIS_MODULE,
.of_match_table = defendkey_dt_match,
.owner = THIS_MODULE,
},
};
module_platform_driver(defendkey_driver);
MODULE_DESCRIPTION("AMLOGIC defendkey driver");
MODULE_LICENSE("GPL");