// SPDX-License-Identifier: GPL-2.0 | |
/* | |
* Copyright (C) 2023 ASR Micro Limited | |
* | |
*/ | |
#include <linux/module.h> | |
#include <linux/kernel.h> | |
#include <linux/platform_device.h> | |
#include <linux/of.h> | |
#include <linux/clk.h> | |
#include <linux/io.h> | |
#include <linux/slab.h> | |
#include <linux/miscdevice.h> | |
#include <linux/sched.h> | |
#include <linux/fs.h> | |
#include <linux/uaccess.h> | |
#ifdef CONFIG_TEE | |
#include <linux/tee_drv.h> | |
#endif | |
#include <uapi/linux/geu_ioctl.h> | |
#include "asr-fuse-optee.h" | |
#include "asr-geu-optee.h" | |
static struct teec_uuid pta_fuse_uuid = ASR_FUSE_ACCESS_UUID; | |
static int asr_fuse_read_sim_lock(int *sim_lock) | |
{ | |
return asrgeu_optee_acquire_ta_buff(&pta_fuse_uuid, CMD_FUSE_READ_SIM_LOCK, sim_lock, 4, 0); | |
} | |
static int asr_fuse_burn_sim_lock(void) | |
{ | |
int sim_lock = 0; | |
asrgeu_optee_acquire_ta_buff(&pta_fuse_uuid, CMD_FUSE_BURN_SIM_LOCK, &sim_lock, 4, 0); | |
return sim_lock; | |
} | |
static int asr_fuse_get_trusted_boot_mode(int *tb_mode) | |
{ | |
return asrgeu_optee_acquire_ta_buff(&pta_fuse_uuid, CMD_FUSE_READ_TRUSTED_MODE, tb_mode, 4, 0); | |
} | |
static int asr_fuse_read_oem_key_hash(uint32_t *key_hash) | |
{ | |
return asrgeu_optee_acquire_ta_buff(&pta_fuse_uuid, CMD_FUSE_READ_OEM_KEY_HASH, key_hash, 32, 0); | |
} | |
static int asr_fuse_read_life_cycle(uint32_t *life_cycle) | |
{ | |
return asrgeu_optee_acquire_ta_buff(&pta_fuse_uuid, CMD_FUSE_READ_LIFE_CYCLE, life_cycle, 2, 0); | |
} | |
static int asr_fuse_read_oem_uid(uint32_t *uid) | |
{ | |
return asrgeu_optee_acquire_ta_buff(&pta_fuse_uuid, CMD_FUSE_READ_OEM_UUID, uid, 8, 0); | |
} | |
static DEFINE_MUTEX(sim_lock_mutex); | |
static DEFINE_MUTEX(tb_mode_mutex); | |
static ssize_t sim_lock_read(struct device *dev, | |
struct device_attribute *attr, char *buf) | |
{ | |
int len = 0; | |
unsigned int sim_lock; | |
mutex_lock(&sim_lock_mutex); | |
asr_fuse_read_sim_lock(&sim_lock); | |
len = sprintf(buf, "%x\n", sim_lock); | |
mutex_unlock(&sim_lock_mutex); | |
return (ssize_t)len; | |
} | |
static ssize_t sim_lock_write(struct device *dev, | |
struct device_attribute *attr, const char *buf, size_t size) | |
{ | |
int ret, enable, sim_lock; | |
ret = sscanf(buf, "%d", &enable); | |
if(ret != 1 || enable != 1) { | |
return size; | |
} | |
mutex_lock(&sim_lock_mutex); | |
sim_lock = asr_fuse_burn_sim_lock(); | |
if (!sim_lock) | |
dev_info(dev, "burn sim lock fails\n"); | |
else | |
dev_info(dev, "burn sim lock done\n"); | |
mutex_unlock(&sim_lock_mutex); | |
return size; | |
} | |
static ssize_t tb_mode_read(struct device *dev, | |
struct device_attribute *attr, char *buf) | |
{ | |
int len = 0, i; | |
int tb_mode; | |
unsigned int fused_key_hash[8]; | |
mutex_lock(&tb_mode_mutex); | |
asr_fuse_get_trusted_boot_mode(&tb_mode); | |
asr_fuse_read_oem_key_hash(fused_key_hash); | |
len = sprintf(buf, "Trusted boot mode: %d\n", tb_mode); | |
len += sprintf(buf+len, "OEM Key Hash(High --> Low):\n"); | |
for (i = 7; i >= 0; i--) | |
len += sprintf(buf+len, "%08x ", fused_key_hash[i]); | |
len += sprintf(buf+len, "\n"); | |
mutex_unlock(&tb_mode_mutex); | |
return (ssize_t)len; | |
} | |
static DEVICE_ATTR(simlock, S_IRUGO | S_IWUSR, sim_lock_read, sim_lock_write); | |
static DEVICE_ATTR(tb_mode, S_IRUGO, tb_mode_read, NULL); | |
static struct attribute *fuse_dev_attrs[] = { | |
&dev_attr_simlock.attr, | |
&dev_attr_tb_mode.attr, | |
NULL | |
}; | |
ATTRIBUTE_GROUPS(fuse_dev); | |
static int asr_fuse_iotcl_read_key_hash(void __user *key_hash, unsigned int len) | |
{ | |
unsigned int oem_key_hash[8]; | |
if (!access_ok(key_hash, len)) | |
return -EFAULT; | |
asr_fuse_read_oem_key_hash(oem_key_hash); | |
if (copy_to_user(key_hash, oem_key_hash, len)) { | |
return -EFAULT; | |
} | |
return 0; | |
} | |
static int asr_fuse_iotcl_read_life_cycle(void __user *life_cyle, unsigned int len) | |
{ | |
unsigned int asr_life_cyle[2]; | |
if (!access_ok(life_cyle, 2 * sizeof(unsigned int))) | |
return -EFAULT; | |
asr_fuse_read_life_cycle(asr_life_cyle); | |
if (copy_to_user(life_cyle, asr_life_cyle, sizeof(unsigned int) * 2)) { | |
return -EFAULT; | |
} | |
return 0; | |
} | |
static int asr_fuse_iotcl_read_oem_uid(void __user *oem_uid_hi, void __user *oem_uid_lo) | |
{ | |
unsigned int oem_uid[2]; | |
if (!access_ok(oem_uid_hi, sizeof(unsigned int)) || | |
!access_ok(oem_uid_lo, sizeof(unsigned int))) | |
return -EFAULT; | |
asr_fuse_read_oem_uid(oem_uid); | |
if (copy_to_user(oem_uid_hi, &oem_uid[0], sizeof(unsigned int))) | |
return -EFAULT; | |
if (copy_to_user(oem_uid_lo, &oem_uid[1], sizeof(unsigned int))) | |
return -EFAULT; | |
return 0; | |
} | |
static int asr_fuse_iotcl_burn_simlock(void __user *sim_lock) | |
{ | |
int simlock = 0; | |
simlock = asr_fuse_burn_sim_lock(); | |
if (simlock == 0) { | |
pr_err("asr fuse: burn sim lock fails\n"); | |
return -EFAULT; | |
} else { | |
if(copy_to_user(sim_lock, (void *)(&simlock), 4)) { | |
pr_err("asr fuse: get sim lock error\n"); | |
return -EFAULT; | |
} | |
} | |
return 0; | |
} | |
static int asr_fuse_iotcl_get_simlock(void __user *sim_lock) | |
{ | |
int simlock; | |
asr_fuse_read_sim_lock(&simlock); | |
if(copy_to_user(sim_lock, (void *)(&simlock), 4)) { | |
pr_err("asr fuse: get sim lock error\n"); | |
return -EFAULT; | |
} | |
return 0; | |
} | |
static int asr_fuse_iotcl_get_trust_mode(void __user *tb_mode) | |
{ | |
int trusted_mode; | |
asr_fuse_get_trusted_boot_mode(&trusted_mode); | |
if(copy_to_user(tb_mode, (void *)(&trusted_mode), 4)) { | |
pr_err("asr fuse: get trusted boot error\n"); | |
return -EFAULT; | |
} | |
return 0; | |
} | |
static long asr_fuse_ioctl(struct file *file, u_int cmd, u_long arg) | |
{ | |
int ret = 0; | |
struct geu_arg geu_arg; | |
if (copy_from_user(&geu_arg, (void __user *)arg, sizeof(geu_arg))) | |
return -EFAULT; | |
switch (cmd) { | |
case GEU_READ_OEMHASHKEY: | |
ret = asr_fuse_iotcl_read_key_hash((void __user *)geu_arg.arg0, | |
geu_arg.arg1); | |
break; | |
case GEU_READ_LIFECYCLE: | |
ret = asr_fuse_iotcl_read_life_cycle((void __user *)geu_arg.arg0, | |
geu_arg.arg1); | |
break; | |
case GEU_READ_OEM_UUID: | |
ret = asr_fuse_iotcl_read_oem_uid((void __user *)geu_arg.arg0, | |
(void __user *)geu_arg.arg1); | |
break; | |
case GEU_BURN_SIM_LOCK: | |
ret = asr_fuse_iotcl_burn_simlock((void __user *)arg); | |
break; | |
case GEU_GET_SIM_LOCK: | |
ret = asr_fuse_iotcl_get_simlock((void __user *)arg); | |
break; | |
case GEU_GET_TRUST_MODE: | |
ret = asr_fuse_iotcl_get_trust_mode((void __user *)arg); | |
break; | |
default: | |
pr_err("asr fuse: iotcl invald command %x\n", cmd); | |
ret = -EINVAL; | |
} | |
return ret; | |
} | |
static int asr_fuse_open(struct inode *inode, struct file *file) | |
{ | |
return 0; | |
} | |
static int asr_fuse_close(struct inode *inode, struct file *file) | |
{ | |
return 0; | |
} | |
static const struct file_operations asr_fuse_fops = { | |
.owner = THIS_MODULE, | |
.open = asr_fuse_open, | |
.release = asr_fuse_close, | |
.unlocked_ioctl = asr_fuse_ioctl, | |
}; | |
int asr_geu_fuse_register(struct asr_geu_dev *geu_dd) | |
{ | |
int ret = 0; | |
struct asr_geu_fuse *fuse; | |
struct miscdevice *misc; | |
struct device *dev = geu_dd->dev; | |
fuse = devm_kzalloc(dev, sizeof(struct asr_geu_fuse), GFP_KERNEL); | |
if (!fuse) | |
return -ENOMEM; | |
geu_dd->asr_fuse = fuse; | |
misc = &fuse->fuse_misc; | |
/* register the device */ | |
misc->name = "geu"; /* to be compatiable with old apps */ | |
misc->minor = MISC_DYNAMIC_MINOR; | |
misc->fops = &asr_fuse_fops; | |
misc->this_device = NULL; | |
misc->groups = fuse_dev_groups; | |
ret = misc_register(misc); | |
if (ret < 0) { | |
dev_err(dev, | |
"unable to register device node /dev/geu\n"); | |
devm_kfree(dev, fuse); | |
return ret; | |
} | |
return 0; | |
} | |
int asr_geu_fuse_unregister(struct asr_geu_dev *geu_dd) | |
{ | |
struct device *dev = geu_dd->dev; | |
struct asr_geu_fuse *fuse = geu_dd->asr_fuse; | |
misc_deregister(&fuse->fuse_misc); | |
devm_kfree(dev, fuse); | |
return 0; | |
} | |
MODULE_DESCRIPTION("ASR Fuse Read/Write driver with optee-os."); | |
MODULE_LICENSE("GPL v2"); | |
MODULE_AUTHOR("Yu Zhang"); |