blob: 6d91c8c46071a11324e0baab29eeb931385a3e63 [file] [log] [blame]
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/cputype.h>
#include <uapi/linux/geu_ioctl.h>
#include "asr-geu.h"
#define to_asr_fuse(p) container_of(p, struct asr_geu_fuse, fuse_misc)
static inline u32 fuse_readl(struct asr_geu_fuse *fuse, u32 offset)
{
return readl(fuse->io_base + offset);
}
static inline void fuse_writel(struct asr_geu_fuse *fuse, u32 offset,
u32 val)
{
writel(val, fuse->io_base + offset);
}
static int asr_fuse_wait_for_complete(struct asr_geu_fuse *fuse,
unsigned int bit_mask, int action /*0: clr, 1: set*/)
{
unsigned int status;
unsigned int timeout = 1000000;
while(timeout)
{
status = fuse_readl(fuse, GEU_FUSE_STATUS);
if((action == 1) && (status & bit_mask))
return 0;
if((action == 0) && ((status & bit_mask) == 0))
return 0;
udelay(1);
timeout --;
}
return 1;
}
static int geu_software_reset(struct asr_geu_fuse *fuse)
{
unsigned int config;
config = fuse_readl(fuse, GEU_CONFIG);
config |= GEU_FUSE_SOFTWARE_RESET;
fuse_writel(fuse, GEU_CONFIG, config);
udelay(10);
config &= ~GEU_FUSE_SOFTWARE_RESET;
fuse_writel(fuse, GEU_CONFIG, config);
if(asr_fuse_wait_for_complete(fuse, GEU_FUSE_READY, 1))
return -1;
return 0;
}
static void asr_fuse_read_oem_key_hash(struct asr_geu_fuse *fuse,
unsigned int *key_hash, unsigned int len)
{
int i;
for(i = 0; i < (len / 4); i++)
{
key_hash[i] = fuse_readl(fuse, (GEU_FUSE_VAL_OEM_HASH_KEY + (i << 2)));
}
}
/*
* tb_mode(trusted boot mode):
* 0 - non-trust boot
* 1 - trusted boot, normal configuration
* 2 - trusted boot, op mode is not burned normally, still work
* -1 - op mode is non-trused but oem key hash is burned, wrong mode
* -2 - op mode is burned as trusted boot, but oem key hash is not burned
* -3 - dis_aib_override and dis_strap_override is not burned, but op mode or oem hey hash is burned, wrong mode
* -4 - wrong mode which can be decoded
* -5 - the platform doesn't support to check trusted boot mode
* Normally, we should get 0 or 1 which stands for non-trusted or trusted mode seperately,
* other modes should not happen unless our SW to enable trusted boot doesn't work properly.
*/
static int geu_get_trusted_mode(struct asr_geu_fuse *fuse, int *tb_mode)
{
unsigned value;
struct device *dev;
unsigned int fused_key_hash[8];
unsigned int zeros[8];
int oem_key_burned, op_mode, fused_burned;
dev = fuse->dev;
asr_fuse_read_oem_key_hash(fuse, fused_key_hash, ASR_OEM_KEY_SHA256_SIZE);
memset(zeros, 0, sizeof(unsigned int) * 8);
if(memcmp(fused_key_hash, zeros, sizeof(unsigned int)*8) == 0)
oem_key_burned = 0;
else
oem_key_burned = 1;
if( cpu_is_pxa1826 ())
{
value = fuse_readl(fuse, GEU_FUSE_VAL_APCFG3);
fused_burned = (value >> 30) & 3;
value = fuse_readl(fuse, GEU_SECURITY_CONFIG);
op_mode = (value >> 24) & 3;
} else if(cpu_is_asr1802s() || cpu_is_asr1828() ||
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903())
{
value = fuse_readl(fuse, GEU_FUSE_VAL_APCFG3);
fused_burned = (value >> 14) & 3;
op_mode = (value >> 10) & 3;
} else if(cpu_is_asr1901() || cpu_is_asr1906())
{
value = fuse_readl(fuse, GEU_FUSE_VAL_APCFG3);
fused_burned = (value >> 30) & 3;
op_mode = (value >> 26) & 3;
} else {
dev_err(dev, "unsupported platform\n");
*tb_mode = -5;
goto done;
}
/* dis_strap_override and dis_aid_strap_override are not burned
* usually, fuse of this chip is not burned.
*/
if(fused_burned != 0x3) {
if( oem_key_burned || (op_mode != 0) ){
dev_warn(dev, "weird fuse status\n");
*tb_mode = -3; //op mode or oem key hash should not be burned
} else {
dev_warn_ratelimited(dev, "fuse is not burned\n");
*tb_mode = 0;
}
goto done;
}
if( oem_key_burned && (op_mode == 2) )
*tb_mode = 1; /* trusted boot is enabled */
else if ( oem_key_burned && (op_mode == 1) )
*tb_mode = -1; //non-trused mode but oem key hash burned
else if ( oem_key_burned && (op_mode == 0) )
*tb_mode = 2; /* trusted boot is enabled, but op mode is not burned, can work */
else if( !oem_key_burned && ( (op_mode == 0) || (op_mode == 1) ) )
*tb_mode = 0; /* non-trusted mode */
else if( !oem_key_burned && (op_mode == 2) )
*tb_mode = -2; /* op mode burned as trusted boot, but oem key hash is not burned */
else
*tb_mode = -4; /* unknown status */
done:
return *tb_mode;
}
static int geu_get_sim_Lock(struct asr_geu_fuse *fuse, int *sim_lock)
{
unsigned value;
struct device *dev = fuse->dev;
value = fuse_readl(fuse, GEU_FUSE_VAL_APCFG1);
if( cpu_is_pxa1826 ())
{
value = (value >> 16) & 1;
} else if(cpu_is_asr1802s() || cpu_is_asr1828() ||
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903())
{
value &= 1;
} else if (cpu_is_asr1901() || cpu_is_asr1906()) {
value &= (1 << 23); /* bit23: sim lock */
value = (value) ? 1:0;
} else {
dev_err(dev, "sim lock is not supported by this platform\n");
value = -1;
}
*sim_lock = value;
return value;
}
static int geu_nzas_simlock_burn_enable(struct asr_geu_fuse *fuse)
{
int i;
unsigned int value;
for(i = 0; i < 8; i++)
fuse_writel(fuse, GEU_FUSE_PROG_VAL1+i*4, 0);
fuse_writel(fuse, GEU_FUSE_PROG_VAL1, (1 << 16)); //sim lock bit
fuse_writel(fuse, GEU_CONFIG, 0);
value = (1 << 25) | (1 << 17) | (0 << 18);
fuse_writel(fuse, GEU_CONFIG, value);
value |= (1 << 20);
fuse_writel(fuse, GEU_CONFIG, value);
udelay(2);
value |= (1 << 21);
fuse_writel(fuse, GEU_CONFIG, value);
udelay(2);
value |= (1 << 16); //trigger fuse burning
fuse_writel(fuse, GEU_CONFIG, value);
return 0;
}
static int geu_nza3_simlock_burn_enable(struct asr_geu_fuse *fuse)
{
int i;
unsigned int value;
struct device *dev = fuse->dev;
fuse_writel(fuse, GEU_REGULATOR_CNT, 0x00f00205);
fuse_writel(fuse, GEU_SCLK_DIV_CNTR, 0xe0);
udelay(1000);
if(geu_software_reset(fuse)) {
dev_err(dev, "fuse software reset fails\n");
return -1;
}
value = (1 << 17);
fuse_writel(fuse, GEU_CONFIG, value); //Enable High Voltage
msleep(10);
for(i = 0; i < 8; i++)
fuse_writel(fuse, GEU_FUSE_PROG_VAL1+i*4, 0);
fuse_writel(fuse, GEU_FUSE_PROG_VAL1, (1 << 16)); //sim lock bit
value |= (7 << 25); //fuse serial clock divider
value |= (0 << 18); //block 0
value |= (1 << 14); //Enable Software Fuse Programming
value |= (1 << 16); //burn enable
fuse_writel(fuse, GEU_CONFIG, value);
return 0;
}
static int geu_kestrel_simlock_burn_enable(struct asr_geu_fuse *fuse)
{
int i;
unsigned int value;
for(i = 0; i < 8; i++)
fuse_writel(fuse, GEU_FUSE_PROG_VAL1+i*4, 0);
fuse_writel(fuse, GEU_FUSE_PROG_VAL1, (1 << 23)); // bank0_bit23: sim lock bit
fuse_writel(fuse, GEU_CONFIG, 0);
value = (7 << 25) | (1 << 17) | (0 << 18) | (1 << 14);
fuse_writel(fuse, GEU_CONFIG, value);
udelay(2);
value |= (1 << 16); //trigger fuse burning
fuse_writel(fuse, GEU_CONFIG, value);
return 0;
}
static int geu_burn_sim_lock(struct asr_geu_fuse *fuse)
{
int i;
int ret = -1;
unsigned int value, scratch;
struct device *dev = fuse->dev;
udelay(1000);
scratch = fuse_readl(fuse, GEU_CONFIG);
if(cpu_is_pxa1826()){
ret = geu_nza3_simlock_burn_enable(fuse);
if(ret)
goto done;
} else if(cpu_is_asr1802s() || cpu_is_asr1828() ||
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903())
{
ret = geu_nzas_simlock_burn_enable(fuse);
if (ret)
goto done;
} else if (cpu_is_asr1901() || cpu_is_asr1906()) {
ret = geu_kestrel_simlock_burn_enable(fuse);
if (ret)
goto done;
} else {
dev_err(dev, "sim lock is not supported by this platform\n");
goto done;
}
/* waiting for burning done */
if(asr_fuse_wait_for_complete(fuse, GEU_FUSE_BURN_DONE, 1)) {
dev_err(dev, "fuse burning timeout\n");
goto done;
}
value = fuse_readl(fuse, GEU_CONFIG);
value &= ~(1 << 16); //clear burn enable
fuse_writel(fuse, GEU_CONFIG, value);
if(asr_fuse_wait_for_complete(fuse, GEU_FUSE_BURN_DONE, 0)){
dev_err(dev, "clear fuse burning enable fails\n");
goto done;
}
if(geu_software_reset(fuse)) {
dev_err(dev, "fuse software reset fails\n");
goto done;
}
geu_get_sim_Lock(fuse, &value);
if(value != 1) {
dev_err(dev, "sim lock fails\n");
goto done;
}
done:
fuse_writel(fuse, GEU_CONFIG, scratch);
for(i = 0; i < 8; i++)
fuse_writel(fuse, GEU_FUSE_PROG_VAL1+i*4, 0);
return ret;
}
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;
struct miscdevice *miscdev = dev_get_drvdata(dev);
struct asr_geu_fuse *fuse = to_asr_fuse(miscdev);
struct asr_geu_dev *geu_dd = dev_get_drvdata(fuse->dev);
struct asr_geu_ops *geu_ops = geu_dd->geu_ops;
mutex_lock(&sim_lock_mutex);
geu_ops->dev_get(geu_dd);
geu_get_sim_Lock(fuse, &sim_lock);
len = sprintf(buf, "%x\n", sim_lock);
geu_ops->dev_put(geu_dd);
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 enable, ret;
struct miscdevice *miscdev = dev_get_drvdata(dev);
struct asr_geu_fuse *fuse = to_asr_fuse(miscdev);
struct asr_geu_dev *geu_dd = dev_get_drvdata(fuse->dev);
struct asr_geu_ops *geu_ops = geu_dd->geu_ops;
ret = sscanf(buf, "%d", &enable);
if(ret != 1 || enable != 1) {
return size;
}
mutex_lock(&sim_lock_mutex);
geu_ops->dev_get(geu_dd);
ret = geu_burn_sim_lock(fuse);
if (ret)
dev_info(dev, "burn sim lock fails\n");
else
dev_info(dev, "burn sim lock done\n");
geu_ops->dev_put(geu_dd);
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;
struct miscdevice *miscdev;
struct asr_geu_dev *geu_dd;
struct asr_geu_ops *geu_ops;
struct asr_geu_fuse *fuse;
unsigned int fused_key_hash[8];
miscdev = dev_get_drvdata(dev);
fuse = to_asr_fuse(miscdev);
geu_dd = dev_get_drvdata(fuse->dev);
geu_ops = geu_dd->geu_ops;
mutex_lock(&tb_mode_mutex);
geu_ops->dev_get(geu_dd);
geu_get_trusted_mode(fuse, &tb_mode);
asr_fuse_read_oem_key_hash(fuse, fused_key_hash, ASR_OEM_KEY_SHA256_SIZE);
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");
geu_ops->dev_put(geu_dd);
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(struct asr_geu_fuse *fuse,
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(fuse, oem_key_hash, ASR_OEM_KEY_SHA256_SIZE);
if (copy_to_user(key_hash, oem_key_hash, len)) {
return -EFAULT;
}
return 0;
}
static int asr_fuse_iotcl_read_life_cycle(struct asr_geu_fuse *fuse,
void __user *life_cyle, unsigned int len)
{
unsigned int asr_life_cyle[2];
if (!access_ok(life_cyle, 2 * sizeof(unsigned int)))
return -EFAULT;
#if !defined(CONFIG_CPU_ASR1901)
if (cpu_is_asr1802s() || cpu_is_asr1828() ||
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903())
{
asr_life_cyle[0] = fuse_readl(fuse, GEU_FUSE_BANK0_192TO207) & 0x7FFF;
}
#else
if (cpu_is_asr1901() || cpu_is_asr1906())
{
asr_life_cyle[0] = fuse_readl(fuse, GEU_KSTR_BANK6_LCS) & 0xFFF;
}
#endif
else {
asr_life_cyle[0] = 0;
}
asr_life_cyle[1] = 0;
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(struct asr_geu_fuse *fuse,
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;
#if !defined(CONFIG_CPU_ASR1901)
if (cpu_is_asr1802s() || cpu_is_asr1828() ||
cpu_is_asr1803() || cpu_is_asr1806() || cpu_is_asr1903())
{
oem_uid[0] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_H);
oem_uid[1] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_L);
} else if (cpu_is_asr1903())
{
oem_uid[0] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_H_LAPW);
oem_uid[1] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_L_LAPW);
}
#else
if (cpu_is_asr1901() || cpu_is_asr1906())
{
oem_uid[0] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_H);
oem_uid[1] = fuse_readl(fuse, GEU_FUSE_VAL_OEM_UID_L);
}
#endif
else {
oem_uid[0] = oem_uid[1] = 0;
}
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(struct asr_geu_fuse *fuse,
void __user *sim_lock)
{
int simlock;
struct device *dev = fuse->dev;
if (geu_burn_sim_lock(fuse)) {
dev_err(dev, "burn sim lock fails\n");
return -EFAULT;
} else {
geu_get_sim_Lock(fuse, &simlock);
if(copy_to_user(sim_lock, (void *)(&simlock), 4)) {
dev_err(dev, "get sim lock error\n");
return -EFAULT;
}
}
return 0;
}
static int asr_fuse_iotcl_get_simlock(struct asr_geu_fuse *fuse,
void __user *sim_lock)
{
int simlock;
struct device *dev = fuse->dev;
geu_get_sim_Lock(fuse, &simlock);
if(copy_to_user(sim_lock, (void *)(&simlock), 4)) {
dev_err(dev, "get sim lock error\n");
return -EFAULT;
}
return 0;
}
static int asr_fuse_iotcl_get_trust_mode(struct asr_geu_fuse *fuse,
void __user *tb_mode)
{
int trusted_mode;
struct device *dev = fuse->dev;
geu_get_trusted_mode(fuse, &trusted_mode);
if(copy_to_user(tb_mode, (void *)(&trusted_mode), 4)) {
dev_err(dev, "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 miscdevice *miscdev;
struct asr_geu_dev *geu_dd;
struct asr_geu_fuse *fuse;
struct asr_geu_ops *geu_ops;
struct device *dev;
struct geu_arg geu_arg;
miscdev = file->private_data;
fuse = to_asr_fuse(miscdev);
geu_dd = dev_get_drvdata(fuse->dev);
geu_ops = geu_dd->geu_ops;
dev = fuse->dev;
if (copy_from_user(&geu_arg, (void __user *)arg, sizeof(geu_arg)))
return -EFAULT;
geu_ops->dev_get(geu_dd);
switch (cmd) {
case GEU_READ_OEMHASHKEY:
ret = asr_fuse_iotcl_read_key_hash(fuse, (void __user *)geu_arg.arg0,
geu_arg.arg1);
break;
case GEU_READ_LIFECYCLE:
ret = asr_fuse_iotcl_read_life_cycle(fuse, (void __user *)geu_arg.arg0,
geu_arg.arg1);
break;
case GEU_READ_OEM_UUID:
ret = asr_fuse_iotcl_read_oem_uid(fuse, (void __user *)geu_arg.arg0,
(void __user *)geu_arg.arg1);
break;
case GEU_BURN_SIM_LOCK:
ret = asr_fuse_iotcl_burn_simlock(fuse, (void __user *)arg);
break;
case GEU_GET_SIM_LOCK:
ret = asr_fuse_iotcl_get_simlock(fuse, (void __user *)arg);
break;
case GEU_GET_TRUST_MODE:
ret = asr_fuse_iotcl_get_trust_mode(fuse, (void __user *)arg);
break;
default:
dev_err(dev, "asr geu: iotcl invald command %x\n", cmd);
ret = -EINVAL;
}
geu_ops->dev_put(geu_dd);
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 *pfuse;
struct miscdevice *misc;
struct device *dev = geu_dd->dev;
pfuse = &geu_dd->asr_fuse;
misc = &pfuse->fuse_misc;
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;
pfuse->io_base = geu_dd->io_base;
pfuse->dev = geu_dd->dev;
pfuse->geu_dd = geu_dd;
/* register the device */
ret = misc_register(misc);
if (ret < 0) {
dev_err(dev,
"asr fuse: unable to register device node /dev/geu\n");
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(asr_geu_fuse_register);
int asr_geu_fuse_unregister(struct asr_geu_dev *geu_dd)
{
struct miscdevice *miscdev;
miscdev = &geu_dd->asr_fuse.fuse_misc;
misc_deregister(miscdev);
device_remove_file(geu_dd->dev, &dev_attr_simlock);
device_remove_file(geu_dd->dev, &dev_attr_tb_mode);
return 0;
}
EXPORT_SYMBOL_GPL(asr_geu_fuse_unregister);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yu Zhang <yuzhang@asrmicro.com>");
MODULE_DESCRIPTION("ASR eFuse driver");