summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVarun Wadekar <vwadekar@nvidia.com>2011-03-13 17:40:30 +0530
committerVarun Colbert <vcolbert@nvidia.com>2011-04-01 19:25:44 -0700
commit4c92e4f2ac11913c4ba19d3ffc1be0f5ae8c4ca6 (patch)
treea8ce8f0f4337fb699cdcc2360e34256383098861
parente09e4554f46532b1bea0f13d444e81efbb361fff (diff)
ARM: tegra: fuse: expose fuses through sysfs
fuses can be read/written via sysfs entries. fuse sysfs entries will be present under /sys/firmware/fuse Based on work done by Venu Byravarasu <vbyravarasu@nvidia.com> Change-Id: Iadb0a83671c8823c541f6bcc2f5f5583d750c1ed Signed-off-by: Varun Wadekar <vwadekar@nvidia.com> Reviewed-on: http://git-master/r/22763 Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r--arch/arm/mach-tegra/include/mach/tegra2_fuse.h25
-rw-r--r--arch/arm/mach-tegra/tegra2_fuse.c251
2 files changed, 265 insertions, 11 deletions
diff --git a/arch/arm/mach-tegra/include/mach/tegra2_fuse.h b/arch/arm/mach-tegra/include/mach/tegra2_fuse.h
index b5341b77a360..21be82a8630b 100644
--- a/arch/arm/mach-tegra/include/mach/tegra2_fuse.h
+++ b/arch/arm/mach-tegra/include/mach/tegra2_fuse.h
@@ -23,21 +23,24 @@
#define SBK_DEVKEY_STATUS_SZ sizeof(u32)
-/* fuse io parameters */
+/*
+ * fuse io parameters: params with sizes less than a byte are
+ * explicitly mentioned
+ */
enum fuse_io_param {
DEVKEY,
- JTAG_DIS,
+ JTAG_DIS, /* 1 bit long */
/*
* Programming the odm production fuse at the same
* time as the sbk or dev_key is not allowed as it is not possible to
* verify that the sbk or dev_key were programmed correctly.
*/
- ODM_PROD_MODE,
+ ODM_PROD_MODE, /* 1 bit long */
SEC_BOOT_DEV_CFG,
- SEC_BOOT_DEV_SEL,
+ SEC_BOOT_DEV_SEL, /* 3 bits long */
SBK,
- SW_RSVD,
- IGNORE_DEV_SEL_STRAPS,
+ SW_RSVD, /* 4 bits long */
+ IGNORE_DEV_SEL_STRAPS, /* 1 bit long */
ODM_RSVD,
SBK_DEVKEY_STATUS,
MASTER_ENB,
@@ -76,16 +79,16 @@ enum {
* @param: io_param_type - param type enum
* @param: size - read size in bytes
*/
-int tegra_fuse_read(u32 io_param_type, u32 *data, int size);
+int tegra_fuse_read(enum fuse_io_param io_param_type, u32 *data, int size);
#define FLAGS_DEVKEY BIT(DEVKEY)
#define FLAGS_JTAG_DIS BIT(JTAG_DIS)
-#define FLAGS_SBK_DEVKEY_STATUS BIT(SBK_DEVKEY_STATUS)
+#define FLAGS_SBK_DEVKEY_STATUS BIT(SBK_DEVKEY_STATUS)
#define FLAGS_ODM_PROD_MODE BIT(ODM_PROD_MODE)
-#define FLAGS_SEC_BOOT_DEV_CFG BIT(SEC_BOOT_DEV_CFG)
-#define FLAGS_SEC_BOOT_DEV_SEL BIT(SEC_BOOT_DEV_SEL)
+#define FLAGS_SEC_BOOT_DEV_CFG BIT(SEC_BOOT_DEV_CFG)
+#define FLAGS_SEC_BOOT_DEV_SEL BIT(SEC_BOOT_DEV_SEL)
#define FLAGS_SBK BIT(SBK)
-#define FLAGS_SW_RSVD BIT(SW_RSVD)
+#define FLAGS_SW_RSVD BIT(SW_RSVD)
#define FLAGS_IGNORE_DEV_SEL_STRAPS BIT(IGNORE_DEV_SEL_STRAPS)
#define FLAGS_ODMRSVD BIT(ODM_RSVD)
diff --git a/arch/arm/mach-tegra/tegra2_fuse.c b/arch/arm/mach-tegra/tegra2_fuse.c
index a488a392716c..6da0c5d7c885 100644
--- a/arch/arm/mach-tegra/tegra2_fuse.c
+++ b/arch/arm/mach-tegra/tegra2_fuse.c
@@ -39,6 +39,11 @@
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+#include <linux/regulator/consumer.h>
+#include <linux/ctype.h>
+#include <linux/wakelock.h>
#include <mach/tegra2_fuse.h>
@@ -60,6 +65,39 @@
#define FUSE_DIS_PGM 0x02C
#define FUSE_PWR_GOOD_SW 0x034
+static struct kobject *fuse_kobj;
+
+static ssize_t fuse_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
+static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count);
+
+static struct kobj_attribute devkey_attr =
+ __ATTR(device_key, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute jtagdis_attr =
+ __ATTR(jtag_disable, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute odm_prod_mode_attr =
+ __ATTR(odm_production_mode, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute sec_boot_dev_cfg_attr =
+ __ATTR(sec_boot_dev_cfg, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute sec_boot_dev_sel_attr =
+ __ATTR(sec_boot_dev_sel, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute sbk_attr =
+ __ATTR(secure_boot_key, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute sw_rsvd_attr =
+ __ATTR(sw_reserved, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute ignore_dev_sel_straps_attr =
+ __ATTR(ignore_dev_sel_straps, 0440, fuse_show, fuse_store);
+
+static struct kobj_attribute odm_rsvd_attr =
+ __ATTR(odm_reserved, 0440, fuse_show, fuse_store);
+
static u32 fuse_pgm_data[NFUSES / 2];
static u32 fuse_pgm_mask[NFUSES / 2];
static u32 tmp_fuse_pgm_data[NFUSES / 2];
@@ -68,6 +106,9 @@ static u32 master_enable;
DEFINE_MUTEX(fuse_lock);
static struct fuse_data fuse_info;
+struct regulator *vdd_fuse = NULL;
+
+#define FUSE_NAME_LEN 30
struct param_info {
u32 *addr;
@@ -76,6 +117,7 @@ struct param_info {
int start_bit;
int nbits;
int data_offset;
+ char sysfs_name[FUSE_NAME_LEN];
};
static struct param_info fuse_info_tbl[] = {
@@ -86,6 +128,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 8,
.nbits = 32,
.data_offset = 0,
+ .sysfs_name = "device_key",
},
[JTAG_DIS] = {
.addr = &fuse_info.jtag_dis,
@@ -94,6 +137,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 24,
.nbits = 1,
.data_offset = 1,
+ .sysfs_name = "jtag_disable",
},
[ODM_PROD_MODE] = {
.addr = &fuse_info.odm_prod_mode,
@@ -102,6 +146,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 23,
.nbits = 1,
.data_offset = 2,
+ .sysfs_name = "odm_production_mode",
},
[SEC_BOOT_DEV_CFG] = {
.addr = &fuse_info.bootdev_cfg,
@@ -110,6 +155,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 8,
.nbits = 16,
.data_offset = 3,
+ .sysfs_name = "sec_boot_dev_cfg",
},
[SEC_BOOT_DEV_SEL] = {
.addr = &fuse_info.bootdev_sel,
@@ -118,6 +164,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 24,
.nbits = 3,
.data_offset = 4,
+ .sysfs_name = "sec_boot_dev_sel",
},
[SBK] = {
.addr = fuse_info.sbk,
@@ -126,6 +173,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 8,
.nbits = 128,
.data_offset = 5,
+ .sysfs_name = "secure_boot_key",
},
[SW_RSVD] = {
.addr = &fuse_info.sw_rsvd,
@@ -134,6 +182,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 28,
.nbits = 4,
.data_offset = 9,
+ .sysfs_name = "sw_reserved",
},
[IGNORE_DEV_SEL_STRAPS] = {
.addr = &fuse_info.ignore_devsel_straps,
@@ -142,6 +191,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 27,
.nbits = 1,
.data_offset = 10,
+ .sysfs_name = "ignore_dev_sel_straps",
},
[ODM_RSVD] = {
.addr = fuse_info.odm_rsvd,
@@ -150,6 +200,7 @@ static struct param_info fuse_info_tbl[] = {
.start_bit = 4,
.nbits = 256,
.data_offset = 11,
+ .sysfs_name = "odm_reserved",
},
[SBK_DEVKEY_STATUS] = {
.sz = SBK_DEVKEY_STATUS_SZ,
@@ -520,6 +571,11 @@ int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags)
return -EPERM;
}
+ if (IS_ERR_OR_NULL(vdd_fuse)) {
+ pr_err("no regulator. fuse programming disabled\n");
+ return -EPERM;
+ }
+
mutex_lock(&fuse_lock);
memcpy(&fuse_info, pgm_data, sizeof(fuse_info));
for_each_set_bit(i, (unsigned long *)&flags, MAX_PARAMS) {
@@ -527,6 +583,7 @@ int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags)
fuse_info_tbl[i].sz);
}
+ regulator_enable(vdd_fuse);
populate_fuse_arrs(&fuse_info, flags);
fuse_program_array(0);
@@ -535,6 +592,7 @@ int tegra_fuse_program(struct fuse_data *pgm_data, u32 flags)
set_fuse(MASTER_ENB, &reg);
memset(&fuse_info, 0, sizeof(fuse_info));
+ regulator_disable(vdd_fuse);
mutex_unlock(&fuse_lock);
return 0;
@@ -547,10 +605,203 @@ void tegra_fuse_program_disable(void)
mutex_unlock(&fuse_lock);
}
+static int fuse_name_to_param(const char *str)
+{
+ int i;
+
+ for (i = DEVKEY; i < ARRAY_SIZE(fuse_info_tbl); i++) {
+ if (!strcmp(str, fuse_info_tbl[i].sysfs_name))
+ return i;
+ }
+
+ return -ENODATA;
+}
+
+static int char_to_xdigit(int c)
+{
+ return (c>='0' && c<='9') ? c - '0' :
+ (c>='a' && c<='f') ? c - 'a' + 10 :
+ (c>='A' && c<='F') ? c - 'A' + 10 : -1;
+}
+
+#define CHK_ERR(x) \
+{ \
+ if (x) \
+ { \
+ pr_err("%s: sysfs_create_file fail(%d)!", __func__, x); \
+ return x; \
+ } \
+}
+
+#define CHARS_PER_WORD 8
+
+static ssize_t fuse_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ enum fuse_io_param param = fuse_name_to_param(attr->attr.name);
+ int ret, i = 0;
+ struct fuse_data data = {0};
+ u32 *raw_data = ((u32 *)&data) + fuse_info_tbl[param].data_offset;
+ struct wake_lock fuse_wk_lock;
+
+ if ((param == -1) || (param == -ENODATA)) {
+ pr_err("%s: invalid fuse\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!isxdigit(*buf)) {
+ pr_err("%s: isxdigit fail\n", __func__);
+ return count;
+ }
+
+ if (fuse_odm_prod_mode()) {
+ pr_err("%s: device locked. odm fuse already blown\n", __func__);
+ return 0;
+ }
+
+ count--;
+ if (DIV_ROUND_UP(count, 2) > fuse_info_tbl[param].sz) {
+ pr_err("%s: fuse parameter too long, should be %d bytes\n",
+ __func__, fuse_info_tbl[param].sz * 2);
+ return -EINVAL;
+ }
+
+ /* wakelock to avoid device powering down while programming */
+ wake_lock_init(&fuse_wk_lock, WAKE_LOCK_SUSPEND, "fuse_wk_lock");
+ wake_lock(&fuse_wk_lock);
+
+ raw_data += (count / CHARS_PER_WORD);
+ *raw_data = 0;
+ while (isxdigit(*buf)) {
+ *raw_data <<= 4;
+ *raw_data += char_to_xdigit(*buf);
+ buf++;
+ if (++i == 8) {
+ raw_data--;
+ *raw_data = 0;
+ i = 0;
+ }
+ }
+
+ ret = tegra_fuse_program(&data, BIT(param));
+ if (ret) {
+ wake_unlock(&fuse_wk_lock);
+ wake_lock_destroy(&fuse_wk_lock);
+ pr_err("%s: fuse program fail(%d)\n", __func__, ret);
+ return ret;
+ }
+
+ /* if odm prodn mode fuse is burnt, change file permissions to 0440 */
+ if (param == ODM_PROD_MODE) {
+ CHK_ERR(sysfs_chmod_file(kobj, &attr->attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &devkey_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &jtagdis_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &sec_boot_dev_cfg_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &sec_boot_dev_sel_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &sbk_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &sw_rsvd_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &ignore_dev_sel_straps_attr.attr, 0440));
+ CHK_ERR(sysfs_chmod_file(kobj, &odm_rsvd_attr.attr, 0440));
+ }
+
+ wake_unlock(&fuse_wk_lock);
+ wake_lock_destroy(&fuse_wk_lock);
+ return count;
+}
+
+static ssize_t fuse_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ enum fuse_io_param param = fuse_name_to_param(attr->attr.name);
+ u32 data[8];
+ char str[8];
+ int ret, i;
+
+ if ((param == -1) || (param == -ENODATA)) {
+ pr_err("%s: invalid fuse\n", __func__);
+ return -EINVAL;
+ }
+
+ if ((param == SBK) && fuse_odm_prod_mode()) {
+ pr_err("device locked. sbk read not allowed\n");
+ return 0;
+ }
+
+ memset(data, 0, sizeof(data));
+ ret = tegra_fuse_read(param, data, fuse_info_tbl[param].sz);
+ if (ret) {
+ pr_err("%s: read fail(%d)\n", __func__, ret);
+ return ret;
+ }
+
+ strcpy(buf, "");
+ for (i = 0; i < (fuse_info_tbl[param].sz/sizeof(u32)) ; i++) {
+ sprintf(str, "%08x", data[i]);
+ strcat(buf, str);
+ }
+
+ strcat(buf, "\n");
+ return strlen(buf);
+}
+
static int __init tegra_fuse_program_init(void)
{
+ /* get vfuse regulator */
+ vdd_fuse = regulator_get(NULL, "vdd_fuse");
+ if (IS_ERR_OR_NULL(vdd_fuse))
+ pr_err("%s: could not get vdd_fuse. fuse programming disabled\n", __func__);
+
+ fuse_kobj = kobject_create_and_add("fuse", firmware_kobj);
+ if (!fuse_kobj) {
+ pr_err("%s: fuse_kobj create fail\n", __func__);
+ return -ENODEV;
+ }
+
mutex_init(&fuse_lock);
+
+ /* change fuse file permissions, if ODM production fuse is not blown */
+ if (!fuse_odm_prod_mode())
+ {
+ devkey_attr.attr.mode = 0640;
+ jtagdis_attr.attr.mode = 0640;
+ odm_prod_mode_attr.attr.mode = 0640;
+ sec_boot_dev_cfg_attr.attr.mode = 0640;
+ sec_boot_dev_sel_attr.attr.mode = 0640;
+ sbk_attr.attr.mode = 0640;
+ sw_rsvd_attr.attr.mode = 0640;
+ ignore_dev_sel_straps_attr.attr.mode = 0640;
+ odm_rsvd_attr.attr.mode = 0640;
+ odm_prod_mode_attr.attr.mode = 0640;
+ }
+
+ CHK_ERR(sysfs_create_file(fuse_kobj, &odm_prod_mode_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &devkey_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &jtagdis_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &sec_boot_dev_cfg_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &sec_boot_dev_sel_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &sbk_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &sw_rsvd_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &ignore_dev_sel_straps_attr.attr));
+ CHK_ERR(sysfs_create_file(fuse_kobj, &odm_rsvd_attr.attr));
+
return 0;
}
+static void __exit tegra_fuse_program_exit(void)
+{
+ if (!IS_ERR_OR_NULL(vdd_fuse))
+ regulator_put(vdd_fuse);
+
+ sysfs_remove_file(fuse_kobj, &odm_prod_mode_attr.attr);
+ sysfs_remove_file(fuse_kobj, &devkey_attr.attr);
+ sysfs_remove_file(fuse_kobj, &jtagdis_attr.attr);
+ sysfs_remove_file(fuse_kobj, &sec_boot_dev_cfg_attr.attr);
+ sysfs_remove_file(fuse_kobj, &sec_boot_dev_sel_attr.attr);
+ sysfs_remove_file(fuse_kobj, &sbk_attr.attr);
+ sysfs_remove_file(fuse_kobj, &sw_rsvd_attr.attr);
+ sysfs_remove_file(fuse_kobj, &ignore_dev_sel_straps_attr.attr);
+ sysfs_remove_file(fuse_kobj, &odm_rsvd_attr.attr);
+ kobject_del(fuse_kobj);
+}
+
module_init(tegra_fuse_program_init);
+module_exit(tegra_fuse_program_exit);