blob: e1dd7804a103e9d8dff3951e2c8614461de6b036 [file] [log] [blame]
// Copyright 2011 Google Inc. All Rights Reserved.
// Author: dgentry@google.com (Denny Gentry)
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "hmx_upgrade_nvram.h"
// Max length of data in an NVRAM field
#define NVRAM_MAX_DATA (64*1024)
// Number of bytes of GPN to be represented as hex data
#define GPN_HEX_BYTES 4
// Holds whether -w can create new variables in NVRAM. Set with -n
int can_add_flag = 0;
/* To avoid modifying the HMX code, we supply dummy versions of two
* missing routines to satisfy the linker. These are used when writing
* the complete NVRAM partiton, which we do not need in this utility. */
DRV_Error DRV_NANDFLASH_GetNvramHandle(int handle) {
return DRV_ERR;
}
DRV_Error DRV_FLASH_Write(int offset, char* data, int nDataSize) {
return DRV_ERR;
}
void usage(const char* progname) {
printf("Usage: %s [-d | [-q|-b] [-r|-k] VARNAME] [ [-n [-p [RO|RW]]] -w VARNAME=value]\n", progname);
printf("\t-d : dump all NVRAM variables\n");
printf("\t-r VARNAME : read VARNAME from NVRAM\n");
printf("\t-q : quiet mode, suppress the variable name and equal sign\n");
printf("\t-b : read VARNAME from NVRAM in raw binary format, e.g. dumping a binary key\n");
printf("\t-w VARNAME=value : write value to VARNAME in NVRAM.\n");
printf("\t-n : toggles whether -w can create new variables. Default is off\n");
printf("\t-p [RW|RO] : toggles what partition new writes (-n) used. Default is RW\n");
printf("\t-k VARNAME : delete existing key/value pair from NVRAM.\n");
printf("\t Set environment variable: $HNVRAM_LOCATION to change where read/writes are performed.");
printf("\t By default hnvram uses '/dev/mtd/hnvram'\n");
}
// Format of data in the NVRAM
typedef enum {
HNVRAM_STRING, // NUL-terminated string
HNVRAM_MAC, // 00:11:22:33:44:55
HNVRAM_HMXSWVERS, // 2.15
HNVRAM_UINT8, // a single byte, generally 0/1 for a boolean.
HNVRAM_GPN, // Two formats:
// - 4 bytes (old format): printed as 8 digit hex.
// - > 4 bytes (new format): printed as NULL-terminated
// string.
HNVRAM_HEXSTRING // hexbinary
} hnvram_format_e;
typedef struct hnvram_field_s {
const char* name;
NVRAM_FIELD_T nvram_type; // defined in hmx_upgrade_nvram.h
hnvram_format_e format;
} hnvram_field_t;
const hnvram_field_t nvram_fields[] = {
{"SYSTEM_ID", NVRAM_FIELD_SYSTEM_ID, HNVRAM_STRING},
{"MAC_ADDR", NVRAM_FIELD_MAC_ADDR, HNVRAM_MAC},
{"SERIAL_NO", NVRAM_FIELD_SERIAL_NO, HNVRAM_STRING},
{"LOADER_VERSION", NVRAM_FIELD_LOADER_VERSION, HNVRAM_HMXSWVERS},
{"ACTIVATED_KERNEL_NUM", NVRAM_FIELD_ACTIVATED_KERNEL_NUM, HNVRAM_UINT8},
{"MTD_TYPE_FOR_KERNEL", NVRAM_FIELD_MTD_TYPE_FOR_KERNEL, HNVRAM_STRING},
{"ACTIVATED_KERNEL_NAME", NVRAM_FIELD_ACTIVATED_KERNEL_NAME, HNVRAM_STRING},
{"EXTRA_KERNEL_OPT", NVRAM_FIELD_EXTRA_KERNEL_OPT, HNVRAM_STRING},
{"PLATFORM_NAME", NVRAM_FIELD_PLATFORM_NAME, HNVRAM_STRING},
{"1ST_SERIAL_NUMBER", NVRAM_FIELD_1ST_SERIAL_NUMBER, HNVRAM_STRING},
{"2ND_SERIAL_NUMBER", NVRAM_FIELD_2ND_SERIAL_NUMBER, HNVRAM_STRING},
{"GPN", NVRAM_FIELD_GPN, HNVRAM_GPN},
{"MAC_ADDR_MOCA", NVRAM_FIELD_MAC_ADDR_MOCA, HNVRAM_MAC},
{"MAC_ADDR_BT", NVRAM_FIELD_MAC_ADDR_BT, HNVRAM_MAC},
{"MAC_ADDR_WIFI", NVRAM_FIELD_MAC_ADDR_WIFI, HNVRAM_MAC},
{"MAC_ADDR_WIFI2", NVRAM_FIELD_MAC_ADDR_WIFI2, HNVRAM_MAC},
{"MAC_ADDR_WAN", NVRAM_FIELD_MAC_ADDR_WAN, HNVRAM_MAC},
{"HDCP_KEY", NVRAM_FIELD_HDCP_KEY, HNVRAM_HEXSTRING},
{"DTCP_KEY", NVRAM_FIELD_DTCP_KEY, HNVRAM_HEXSTRING},
{"GOOGLE_SSL_PEM", NVRAM_FIELD_GOOGLE_SSL_PEM, HNVRAM_STRING},
{"GOOGLE_SSL_CRT", NVRAM_FIELD_GOOGLE_SSL_CRT, HNVRAM_STRING},
{"PAIRED_DISK", NVRAM_FIELD_PAIRED_DISK, HNVRAM_STRING},
{"PARTITION_VER", NVRAM_FIELD_PARTITION_VER, HNVRAM_STRING},
{"HW_VER", NVRAM_FIELD_HW_VER, HNVRAM_UINT8},
{"UITYPE", NVRAM_FIELD_UITYPE, HNVRAM_STRING},
{"LASER_CHANNEL", NVRAM_FIELD_LASER_CHANNEL, HNVRAM_STRING},
{"MAC_ADDR_PON", NVRAM_FIELD_MAC_ADDR_PON, HNVRAM_MAC},
{"PRODUCTION_UNIT", NVRAM_FIELD_PRODUCTION_UNIT, HNVRAM_STRING},
{"BOOT_TARGET", NVRAM_FIELD_BOOT_TARGET, HNVRAM_STRING},
{"ANDROID_ACTIVE_PARTITION", NVRAM_FIELD_ANDROID_ACTIVE_PARTITION,
HNVRAM_STRING},
};
const hnvram_field_t* get_nvram_field(const char* name) {
int nentries = sizeof(nvram_fields) / sizeof(nvram_fields[0]);
int i;
for (i = 0; i < nentries; ++i) {
const hnvram_field_t* map = &nvram_fields[i];
if (strcasecmp(name, map->name) == 0) {
return map;
}
}
return NULL;
}
// ------------------ READ NVRAM -----------------------------
void format_string(const unsigned char* data, char* output, int outlen) {
snprintf(output, outlen, "%s", data);
}
void format_mac(const unsigned char* data, char* output, int outlen) {
snprintf(output, outlen, "%02hx:%02hx:%02hx:%02hx:%02hx:%02hx",
data[0], data[1], data[2], data[3], data[4], data[5]);
}
void format_hmxswvers(const unsigned char* data, char* output, int outlen) {
snprintf(output, outlen, "%hhu.%hhu", data[1], data[0]);
}
void format_uint8(const unsigned char* data, char* output, int outlen) {
snprintf(output, outlen, "%u", data[0]);
}
void format_hexstring(const unsigned char* data, int datalen, char* output,
int outlen) {
int i;
if (outlen < (datalen * 2 + 1)) {
fprintf(stderr, "%s buffer too small %d < %d",
__FUNCTION__, outlen, (datalen * 2 + 1));
exit(1);
}
for (i = 0; i < datalen; ++i) {
snprintf(output + (i * 2), 3, "%02x", data[i]);
}
}
void format_gpn(const unsigned char* data, const int data_len, char* output,
int outlen) {
// Format first 4 bytes as 8 digit hex.
if (data_len == GPN_HEX_BYTES)
format_hexstring(data, GPN_HEX_BYTES, output, outlen);
else
format_string(data, output, outlen);
}
char* format_nvram(hnvram_format_e format, const unsigned char* data,
const int data_len, char* output, int outlen) {
output[0] = '\0';
switch(format) {
case HNVRAM_STRING: format_string(data, output, outlen); break;
case HNVRAM_MAC: format_mac(data, output, outlen); break;
case HNVRAM_HMXSWVERS: format_hmxswvers(data, output, outlen); break;
case HNVRAM_UINT8: format_uint8(data, output, outlen); break;
case HNVRAM_GPN: format_gpn(data, data_len, output, outlen); break;
case HNVRAM_HEXSTRING: format_hexstring(data, data_len, output, outlen);
break;
}
return output;
}
int read_raw_nvram(const char* name, char* output, int outlen) {
const hnvram_field_t* field = get_nvram_field(name);
unsigned int ret;
if (field == NULL) {
return -1;
}
if (HMX_NVRAM_GetLength(field->nvram_type, &ret) != DRV_OK) {
return -1;
}
if (ret > outlen) {
return -1;
}
if (HMX_NVRAM_GetField(field->nvram_type, 0, output, outlen) != DRV_OK) {
return -1;
}
return (int)ret;
}
// name - name of key to be read
// output - buffer for value of key
// outlen - length of buffer
// quiet - whether buffer is KEY=VAL or VAL
// part_used - in the case of dynamically added variables (is_field = false),
// returns what partition we found the key in
char* read_nvram(const char* name, char* output, int outlen, int quiet,
HMX_NVRAM_PARTITION_E* part_used) {
const hnvram_field_t* field = get_nvram_field(name);
int is_field = (field != NULL);
unsigned char data[NVRAM_MAX_DATA] = {0};
unsigned int data_len = 0;
hnvram_format_e format_type;
if (is_field) {
format_type = field->format;
if (HMX_NVRAM_GetField(field->nvram_type, 0, data, sizeof(data)) != DRV_OK ||
HMX_NVRAM_GetLength(field->nvram_type, &data_len) != DRV_OK) {
return NULL;
}
} else {
format_type = HNVRAM_STRING;
// Try both partitions
*part_used = HMX_NVRAM_PARTITION_RW;
DRV_Error e = HMX_NVRAM_Read(*part_used, (unsigned char*)name, 0, data,
sizeof(data), &data_len);
if (e != DRV_OK) {
*part_used = HMX_NVRAM_PARTITION_RO;
e = HMX_NVRAM_Read(*part_used, (unsigned char*)name, 0, data,
sizeof(data), &data_len);
if (e != DRV_OK) {
return NULL;
}
}
}
char formatbuf[NVRAM_MAX_DATA * 2];
char* nv = format_nvram(format_type, data, data_len, formatbuf,
sizeof(formatbuf));
if (quiet) {
snprintf(output, outlen, "%s", nv);
} else {
snprintf(output, outlen, "%s=%s", name, nv);
}
return output;
}
// ----------------- WRITE NVRAM -----------------------------
unsigned char* parse_string(const char* input,
unsigned char* output, unsigned int* outlen) {
int len = strlen(input);
if (len > *outlen) {
// Data is too large, don't permit a partial write.
return NULL;
}
strncpy((char*)output, input, len);
*outlen = len;
return output;
}
unsigned char* parse_mac(const char* input,
unsigned char* output, unsigned int* outlen) {
if (*outlen < 6) return NULL;
if (sscanf(input, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
&output[0], &output[1], &output[2],
&output[3], &output[4], &output[5]) != 6) {
return NULL;
}
*outlen = 6;
return output;
}
unsigned char* parse_hmxswvers(const char* input,
unsigned char* output, unsigned int* outlen) {
if (*outlen < 2) return NULL;
if (sscanf(input, "%hhd.%hhd", &output[1], &output[0]) != 2) {
return NULL;
}
*outlen = 2;
return output;
}
unsigned char* parse_uint8(const char* input,
unsigned char* output, unsigned int* outlen) {
if (*outlen < 1) return NULL;
output[0] = input[0] - '0';
*outlen = 1;
return output;
}
int parse_hexdigit(unsigned char c) {
switch(c) {
case '0' ... '9': return c - '0';
case 'a' ... 'f': return 10 + (c - 'a');
case 'A' ... 'F': return 10 + (c - 'A');
default: return 0xff;
}
}
unsigned char* parse_hexstring(const char* input,
unsigned char* output, unsigned int* outlen) {
unsigned int i, len = strlen(input) / 2;
if (*outlen < len) {
len = *outlen;
}
for (i = 0; i < len; ++i) {
unsigned char c;
output[i] = parse_hexdigit(input[2*i]) << 4 |
parse_hexdigit(input[2*i+1]);
}
*outlen = len;
return output;
}
int is_hexstring(const char* input, int hex_len) {
int i = 0;
for (i = 0; i < hex_len; i++) {
if (!isxdigit(input[i])) {
return 0;
}
}
if (input[hex_len] != '\0') {
return 0;
}
return 1;
}
unsigned char* parse_gpn(const char* input,
unsigned char* output, unsigned int* outlen) {
if (*outlen < 4) return NULL;
// Old GPN format: 8-digit hex string
if (is_hexstring(input, GPN_HEX_BYTES * 2)) {
if (sscanf(input, "%02hhx%02hhx%02hhx%02hhx",
&output[0], &output[1], &output[2], &output[3]) != GPN_HEX_BYTES) {
return NULL;
}
*outlen = GPN_HEX_BYTES;
return output;
}
// New GPN format: regular string
return parse_string(input, output, outlen);
}
unsigned char* parse_nvram(hnvram_format_e format, const char* input,
unsigned char* output, unsigned int* outlen) {
output[0] = '\0';
switch(format) {
case HNVRAM_STRING:
return parse_string(input, output, outlen);
break;
case HNVRAM_MAC:
return parse_mac(input, output, outlen);
break;
case HNVRAM_HMXSWVERS:
return parse_hmxswvers(input, output, outlen);
break;
case HNVRAM_UINT8:
return parse_uint8(input, output, outlen);
break;
case HNVRAM_GPN:
return parse_gpn(input, output, outlen);
break;
case HNVRAM_HEXSTRING:
return parse_hexstring(input, output, outlen);
break;
}
return NULL;
}
DRV_Error clear_nvram(const char* optarg) {
DRV_Error err1 = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RW,
(unsigned char*)optarg);
DRV_Error err2 = HMX_NVRAM_Remove(HMX_NVRAM_PARTITION_RO,
(unsigned char*)optarg);
// Avoid throwing error message if variable already cleared
if ((err1 == DRV_ERR || err1 == DRV_OK) &&
(err2 == DRV_ERR || err2 == DRV_OK)) {
return DRV_OK;
}
fprintf(stderr, "Error while deleting key %s. RW: %d RO: %d.\n", optarg,
err1, err2);
return DRV_ERR;
}
int write_nvram(const char* name, const char* value,
HMX_NVRAM_PARTITION_E desired_part) {
const hnvram_field_t* field = get_nvram_field(name);
int is_field = (field != NULL);
hnvram_format_e format_type;
if (is_field) {
format_type = field->format;
} else {
format_type = HNVRAM_STRING;
}
if (strlen(value) > NVRAM_MAX_DATA) {
fprintf(stderr, "Value length %d exceeds maximum data size of %d\n",
(int)strlen(value), NVRAM_MAX_DATA);
return -1;
}
unsigned char nvram_value[NVRAM_MAX_DATA];
unsigned int nvram_len = sizeof(nvram_value);
if (parse_nvram(format_type, value, nvram_value, &nvram_len) == NULL) {
return -2;
}
if (!is_field) {
char tmp[NVRAM_MAX_DATA] = {0};
HMX_NVRAM_PARTITION_E part_used;
if (read_nvram(name, tmp, NVRAM_MAX_DATA, 1, &part_used) == NULL) {
return -3; // Write failed: Variable not found
}
if (desired_part != HMX_NVRAM_PARTITION_UNSPECIFIED &&
desired_part != part_used) {
fprintf(stderr, "Variable already exists in other partition: %s\n", name);
return -4;
}
DRV_Error er = HMX_NVRAM_Write(part_used, (unsigned char*)name, 0,
nvram_value, nvram_len);
if (er != DRV_OK) {
return -5;
}
} else {
if (desired_part != HMX_NVRAM_PARTITION_UNSPECIFIED) {
fprintf(stderr, "Partition was specified (%d) on a field variable: %s\n",
desired_part, name);
return -6;
}
if (HMX_NVRAM_SetField(field->nvram_type, 0,
nvram_value, nvram_len) != DRV_OK) {
return -7;
}
}
return 0;
}
// Adds new variable to HNVRAM in desired_partition as STRING
int write_nvram_new(const char* name, const char* value,
HMX_NVRAM_PARTITION_E desired_part) {
if (!can_add_flag) {
fprintf(stderr, "Key not found in NVRAM. Add -n to allow creation %s\n",
name);
return -1;
}
char tmp[NVRAM_MAX_DATA] = {0};
unsigned char nvram_value[NVRAM_MAX_DATA];
unsigned int nvram_len = sizeof(nvram_value);
if (parse_nvram(HNVRAM_STRING, value, nvram_value, &nvram_len) == NULL) {
return -2;
}
if (desired_part == HMX_NVRAM_PARTITION_UNSPECIFIED) {
desired_part = HMX_NVRAM_PARTITION_RW;
}
DRV_Error er = HMX_NVRAM_Write(desired_part, (unsigned char*)name, 0,
nvram_value, nvram_len);
if (er != DRV_OK) {
return -3;
}
return 0;
}
int init_nvram() {
const char* location = getenv("HNVRAM_LOCATION");
return (int)HMX_NVRAM_Init(location);
}
int hnvram_main(int argc, char* const argv[]) {
DRV_Error err;
libupgrade_verbose = 0;
int ret = init_nvram();
if (ret != 0) {
fprintf(stderr, "NVRAM Init failed: %d\n", ret);
exit(1);
}
char op = 0; // operation
int op_cnt = 0; // operation
int q_flag = 0; // quiet: don't output name of variable.
int b_flag = 0; // binary: output the binary format
// Desired partition for new writes.
HMX_NVRAM_PARTITION_E desired_part = HMX_NVRAM_PARTITION_UNSPECIFIED;
char output[NVRAM_MAX_DATA];
int c;
while ((c = getopt(argc, argv, "dbqrnp:w:k:")) != -1) {
switch(c) {
case 'b':
b_flag = 1;
break;
case 'q':
q_flag = 1;
break;
case 'n':
can_add_flag = 1;
break;
case 'p':
if (strcmp(optarg, "RO") == 0) {
desired_part = HMX_NVRAM_PARTITION_RO;
} else if (strcmp(optarg, "RW") == 0) {
desired_part = HMX_NVRAM_PARTITION_RW;
} else {
fprintf(stderr, "Invalid partition: %s. Use RW or RO\n", optarg);
exit(1);
}
break;
case 'w':
{
char* duparg = strdup(optarg);
char* equal = strchr(duparg, '=');
if (equal == NULL) {
return -1;
}
char* name = duparg;
*equal = '\0';
char* value = equal + 1;
int ret = write_nvram(name, value, desired_part);
if (ret == -3) {
// key not found, try to add a new one
ret = write_nvram_new(name, value, desired_part);
}
if (ret != 0) {
fprintf(stderr, "Err %d: Unable to write %s\n", ret, duparg);
free(duparg);
exit(1);
}
free(duparg);
}
break;
case 'k':
{
char* duparg = strdup(optarg);
if (clear_nvram(duparg) != DRV_OK) {
fprintf(stderr, "Unable to remove key %s\n", duparg);
free(duparg);
exit(1);
}
free(duparg);
}
break;
case 'r':
case 'd':
if (op != c) {
++op_cnt;
}
op = c;
break;
default:
usage(argv[0]);
exit(1);
}
}
if (op_cnt > 1) {
usage(argv[0]);
exit(1);
}
// dump NVRAM at the end, after all writes have been done.
switch (op) {
case 'd':
if (optind < argc) {
usage(argv[0]);
exit(1);
}
if ((err = HMX_NVRAM_Dir()) != DRV_OK) {
fprintf(stderr, "Unable to dump variables, HMX_NVRAM_Dir=%d\n", err);
}
break;
case 'r':
if (optind >= argc) {
usage(argv[0]);
exit(1);
}
for (; optind < argc; ++optind) {
if (b_flag) {
int len = read_raw_nvram(argv[optind], output, sizeof(output));
if (len < 0) {
fprintf(stderr, "Unable to read %s\n", argv[optind]);
exit(1);
}
fwrite(output, 1, len, stdout);
} else {
HMX_NVRAM_PARTITION_E part_used;
if (read_nvram(argv[optind], output, sizeof(output), q_flag, &part_used) == NULL) {
fprintf(stderr, "Unable to read %s\n", argv[optind]);
exit(1);
}
puts(output);
}
}
break;
}
exit(0);
}
#ifndef TEST_MAIN
int main(int argc, char* const argv[]) {
return hnvram_main(argc, argv);
}
#endif // TEST_MAIN