X-Git-Url: https://git.rohieb.name/openwrt.git/blobdiff_plain/796a9d1091884a07817e5c140d0ff6a0b8c76235..094507e8fede9ed92407b1684d7e7106a94a7644:/package/nvram/src/nvram.c diff --git a/package/nvram/src/nvram.c b/package/nvram/src/nvram.c index 47d09a397..ed3da4312 100644 --- a/package/nvram/src/nvram.c +++ b/package/nvram/src/nvram.c @@ -1,9 +1,10 @@ /* - * NVRAM variable manipulation (Linux user mode half) + * NVRAM variable manipulation (common) * * Copyright 2004, Broadcom Corporation + * Copyright 2009, OpenWrt.org * All Rights Reserved. - * + * * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS @@ -11,178 +12,530 @@ * */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "nvram.h" + +#define TRACE(msg) \ + printf("%s(%i) in %s(): %s\n", \ + __FILE__, __LINE__, __FUNCTION__, msg ? msg : "?") -#include -#include -#include +size_t nvram_erase_size = 0; -#define PATH_DEV_NVRAM "/dev/nvram" -/* Globals */ -static int nvram_fd = -1; -static char *nvram_buf = NULL; -int file_to_buf(char *path, char *buf, int len); +/* + * -- Helper functions -- + */ -int -nvram_init(void *unused) +/* String hash */ +static uint32_t hash(const char *s) { - if ((nvram_fd = open(PATH_DEV_NVRAM, O_RDWR)) < 0) - goto err; + uint32_t hash = 0; + + while (*s) + hash = 31 * hash + *s++; - /* Map kernel string buffer into user space */ - if ((nvram_buf = mmap(NULL, NVRAM_SPACE, PROT_READ, MAP_SHARED, nvram_fd, 0)) == MAP_FAILED) { - close(nvram_fd); - nvram_fd = -1; - goto err; + return hash; +} + +/* Free all tuples. */ +static void _nvram_free(nvram_handle_t *h) +{ + uint32_t i; + nvram_tuple_t *t, *next; + + /* Free hash table */ + for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) { + for (t = h->nvram_hash[i]; t; t = next) { + next = t->next; + free(t); + } + h->nvram_hash[i] = NULL; } - return 0; + /* Free dead table */ + for (t = h->nvram_dead; t; t = next) { + next = t->next; + free(t); + } - err: - perror(PATH_DEV_NVRAM); - return errno; + h->nvram_dead = NULL; } -char * -nvram_get(const char *name) +/* (Re)allocate NVRAM tuples. */ +static nvram_tuple_t * _nvram_realloc( nvram_handle_t *h, nvram_tuple_t *t, + const char *name, const char *value ) { - size_t count = strlen(name) + 1; - char tmp[100], *value; - unsigned long *off = (unsigned long *) tmp; + if ((strlen(value) + 1) > NVRAM_SPACE) + return NULL; - if (nvram_fd < 0) - if (nvram_init(NULL)) + if (!t) { + if (!(t = malloc(sizeof(nvram_tuple_t) + strlen(name) + 1))) return NULL; - if (count > sizeof(tmp)) { - if (!(off = malloc(count))) + /* Copy name */ + t->name = (char *) &t[1]; + strcpy(t->name, name); + + t->value = NULL; + } + + /* Copy value */ + if (!t->value || strcmp(t->value, value)) + { + if(!(t->value = (char *) realloc(t->value, strlen(value)+1))) return NULL; + + strcpy(t->value, value); + t->value[strlen(value)] = '\0'; + } + + return t; +} + +/* (Re)initialize the hash table. */ +static int _nvram_rehash(nvram_handle_t *h) +{ + nvram_header_t *header = nvram_header(h); + char buf[] = "0xXXXXXXXX", *name, *value, *eq; + + /* (Re)initialize hash table */ + _nvram_free(h); + + /* Parse and set "name=value\0 ... \0\0" */ + name = (char *) &header[1]; + + for (; *name; name = value + strlen(value) + 1) { + if (!(eq = strchr(name, '='))) + break; + *eq = '\0'; + value = eq + 1; + nvram_set(h, name, value); + *eq = '='; + } + + /* Set special SDRAM parameters */ + if (!nvram_get(h, "sdram_init")) { + sprintf(buf, "0x%04X", (uint16_t)(header->crc_ver_init >> 16)); + nvram_set(h, "sdram_init", buf); + } + if (!nvram_get(h, "sdram_config")) { + sprintf(buf, "0x%04X", (uint16_t)(header->config_refresh & 0xffff)); + nvram_set(h, "sdram_config", buf); + } + if (!nvram_get(h, "sdram_refresh")) { + sprintf(buf, "0x%04X", + (uint16_t)((header->config_refresh >> 16) & 0xffff)); + nvram_set(h, "sdram_refresh", buf); + } + if (!nvram_get(h, "sdram_ncdl")) { + sprintf(buf, "0x%08X", header->config_ncdl); + nvram_set(h, "sdram_ncdl", buf); } - /* Get offset into mmap() space */ - strcpy((char *) off, name); + return 0; +} - count = read(nvram_fd, off, count); - if (count == sizeof(unsigned long)) - value = &nvram_buf[*off]; - else - value = NULL; +/* + * -- Public functions -- + */ - if (count < 0) - perror(PATH_DEV_NVRAM); +/* Get nvram header. */ +nvram_header_t * nvram_header(nvram_handle_t *h) +{ + return (nvram_header_t *) &h->mmap[NVRAM_START(nvram_erase_size)]; +} + +/* Get the value of an NVRAM variable. */ +char * nvram_get(nvram_handle_t *h, const char *name) +{ + uint32_t i; + nvram_tuple_t *t; + char *value; - if (off != (unsigned long *) tmp) - free(off); + if (!name) + return NULL; + + /* Hash the name */ + i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash); + + /* Find the associated tuple in the hash table */ + for (t = h->nvram_hash[i]; t && strcmp(t->name, name); t = t->next); + + value = t ? t->value : NULL; return value; } -int -nvram_getall(char *buf, int count) +/* Set the value of an NVRAM variable. */ +int nvram_set(nvram_handle_t *h, const char *name, const char *value) { - int ret; + uint32_t i; + nvram_tuple_t *t, *u, **prev; + + /* Hash the name */ + i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash); + + /* Find the associated tuple in the hash table */ + for (prev = &h->nvram_hash[i], t = *prev; + t && strcmp(t->name, name); prev = &t->next, t = *prev); + + /* (Re)allocate tuple */ + if (!(u = _nvram_realloc(h, t, name, value))) + return -12; /* -ENOMEM */ + + /* Value reallocated */ + if (t && t == u) + return 0; + + /* Move old tuple to the dead table */ + if (t) { + *prev = t->next; + t->next = h->nvram_dead; + h->nvram_dead = t; + } - if (nvram_fd < 0) - if ((ret = nvram_init(NULL))) - return ret; + /* Add new tuple to the hash table */ + u->next = h->nvram_hash[i]; + h->nvram_hash[i] = u; - if (count == 0) + return 0; +} + +/* Unset the value of an NVRAM variable. */ +int nvram_unset(nvram_handle_t *h, const char *name) +{ + uint32_t i; + nvram_tuple_t *t, **prev; + + if (!name) return 0; - /* Get all variables */ - *buf = '\0'; + /* Hash the name */ + i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash); - ret = read(nvram_fd, buf, count); + /* Find the associated tuple in the hash table */ + for (prev = &h->nvram_hash[i], t = *prev; + t && strcmp(t->name, name); prev = &t->next, t = *prev); - if (ret < 0) - perror(PATH_DEV_NVRAM); + /* Move it to the dead table */ + if (t) { + *prev = t->next; + t->next = h->nvram_dead; + h->nvram_dead = t; + } - return (ret == count) ? 0 : ret; + return 0; } -int -nvram_set(const char *name, const char *value) +/* Get all NVRAM variables. */ +nvram_tuple_t * nvram_getall(nvram_handle_t *h) { - size_t count = strlen(name) + 1; - char tmp[100], *buf = tmp; - int ret; + int i; + nvram_tuple_t *t, *l, *x; + + l = NULL; + + for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) { + for (t = h->nvram_hash[i]; t; t = t->next) { + if( (x = (nvram_tuple_t *) malloc(sizeof(nvram_tuple_t))) != NULL ) + { + x->name = t->name; + x->value = t->value; + x->next = l; + l = x; + } + else + { + break; + } + } + } - if (nvram_fd < 0) - if ((ret = nvram_init(NULL))) - return ret; + return l; +} - /* Unset if value is NULL */ - if (value) - count += strlen(value) + 1; +/* Regenerate NVRAM. */ +int nvram_commit(nvram_handle_t *h) +{ + nvram_header_t *header = nvram_header(h); + char *init, *config, *refresh, *ncdl; + char *ptr, *end; + int i; + nvram_tuple_t *t; + nvram_header_t tmp; + uint8_t crc; + + /* Regenerate header */ + header->magic = NVRAM_MAGIC; + header->crc_ver_init = (NVRAM_VERSION << 8); + if (!(init = nvram_get(h, "sdram_init")) || + !(config = nvram_get(h, "sdram_config")) || + !(refresh = nvram_get(h, "sdram_refresh")) || + !(ncdl = nvram_get(h, "sdram_ncdl"))) { + header->crc_ver_init |= SDRAM_INIT << 16; + header->config_refresh = SDRAM_CONFIG; + header->config_refresh |= SDRAM_REFRESH << 16; + header->config_ncdl = 0; + } else { + header->crc_ver_init |= (strtoul(init, NULL, 0) & 0xffff) << 16; + header->config_refresh = strtoul(config, NULL, 0) & 0xffff; + header->config_refresh |= (strtoul(refresh, NULL, 0) & 0xffff) << 16; + header->config_ncdl = strtoul(ncdl, NULL, 0); + } - if (count > sizeof(tmp)) { - if (!(buf = malloc(count))) - return -ENOMEM; + /* Clear data area */ + ptr = (char *) header + sizeof(nvram_header_t); + memset(ptr, 0xFF, NVRAM_SPACE - sizeof(nvram_header_t)); + memset(&tmp, 0, sizeof(nvram_header_t)); + + /* Leave space for a double NUL at the end */ + end = (char *) header + NVRAM_SPACE - 2; + + /* Write out all tuples */ + for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) { + for (t = h->nvram_hash[i]; t; t = t->next) { + if ((ptr + strlen(t->name) + 1 + strlen(t->value) + 1) > end) + break; + ptr += sprintf(ptr, "%s=%s", t->name, t->value) + 1; + } } - if (value) - sprintf(buf, "%s=%s", name, value); - else - strcpy(buf, name); + /* End with a double NULL and pad to 4 bytes */ + *ptr = '\0'; + ptr++; - ret = write(nvram_fd, buf, count); + if( (int)ptr % 4 ) + memset(ptr, 0, 4 - ((int)ptr % 4)); - if (ret < 0) - perror(PATH_DEV_NVRAM); + ptr++; - if (buf != tmp) - free(buf); + /* Set new length */ + header->len = NVRAM_ROUNDUP(ptr - (char *) header, 4); - return (ret == count) ? 0 : ret; -} + /* Little-endian CRC8 over the last 11 bytes of the header */ + tmp.crc_ver_init = header->crc_ver_init; + tmp.config_refresh = header->config_refresh; + tmp.config_ncdl = header->config_ncdl; + crc = hndcrc8((unsigned char *) &tmp + NVRAM_CRC_START_POSITION, + sizeof(nvram_header_t) - NVRAM_CRC_START_POSITION, 0xff); -int -nvram_unset(const char *name) -{ - return nvram_set(name, NULL); + /* Continue CRC8 over data bytes */ + crc = hndcrc8((unsigned char *) &header[0] + sizeof(nvram_header_t), + header->len - sizeof(nvram_header_t), crc); + + /* Set new CRC8 */ + header->crc_ver_init |= crc; + + /* Write out */ + msync(h->mmap, h->length, MS_SYNC); + fsync(h->fd); + + /* Reinitialize hash table */ + return _nvram_rehash(h); } -int -nvram_commit(void) +/* Open NVRAM and obtain a handle. */ +nvram_handle_t * nvram_open(const char *file, int rdonly) { - int ret; - - if (nvram_fd < 0) - if ((ret = nvram_init(NULL))) - return ret; + int fd; + char *mtd = NULL; + nvram_handle_t *h; + nvram_header_t *header; + + /* If erase size or file are undefined then try to define them */ + if( (nvram_erase_size == 0) || (file == NULL) ) + { + /* Finding the mtd will set the appropriate erase size */ + if( (mtd = nvram_find_mtd()) == NULL || nvram_erase_size == 0 ) + { + free(mtd); + return NULL; + } + } - ret = ioctl(nvram_fd, NVRAM_MAGIC, NULL); + if( (fd = open(file ? file : mtd, O_RDWR)) > -1 ) + { + char *mmap_area = (char *) mmap( + NULL, nvram_erase_size, PROT_READ | PROT_WRITE, + ( rdonly == NVRAM_RO ) ? MAP_PRIVATE : MAP_SHARED, fd, 0); + + if( mmap_area != MAP_FAILED ) + { + memset(mmap_area, 0xFF, NVRAM_START(nvram_erase_size)); + + if((h = (nvram_handle_t *) malloc(sizeof(nvram_handle_t))) != NULL) + { + memset(h, 0, sizeof(nvram_handle_t)); + + h->fd = fd; + h->mmap = mmap_area; + h->length = nvram_erase_size; + + header = nvram_header(h); + + if( header->magic == NVRAM_MAGIC ) + { + _nvram_rehash(h); + free(mtd); + return h; + } + else + { + munmap(h->mmap, h->length); + free(h); + } + } + } + } + + free(mtd); + return NULL; +} - if (ret < 0) - perror(PATH_DEV_NVRAM); +/* Close NVRAM and free memory. */ +int nvram_close(nvram_handle_t *h) +{ + _nvram_free(h); + munmap(h->mmap, h->length); + close(h->fd); + free(h); - return ret; + return 0; } -int -file_to_buf(char *path, char *buf, int len) +/* Determine NVRAM device node. */ +char * nvram_find_mtd(void) { FILE *fp; + int i, esz; + char dev[PATH_MAX]; + char *path = NULL; + struct stat s; + int supported = 1; + + /* Refuse any operation on the WGT634U */ + if( (fp = fopen("/proc/diag/model", "r")) ) + { + if( fgets(dev, sizeof(dev), fp) && !strncmp(dev, "Netgear WGT634U", 15) ) + supported = 0; - memset(buf, 0 , len); + fclose(fp); + } - if ((fp = fopen(path, "r"))) { - fgets(buf, len, fp); + if( supported && (fp = fopen("/proc/mtd", "r")) ) + { + while( fgets(dev, sizeof(dev), fp) ) + { + if( strstr(dev, "nvram") && sscanf(dev, "mtd%d: %08x", &i, &esz) ) + { + nvram_erase_size = esz; + + sprintf(dev, "/dev/mtdblock/%d", i); + if( stat(dev, &s) > -1 && (s.st_mode & S_IFBLK) ) + { + if( (path = (char *) malloc(strlen(dev)+1)) != NULL ) + { + strncpy(path, dev, strlen(dev)+1); + break; + } + } + else + { + sprintf(dev, "/dev/mtdblock%d", i); + if( stat(dev, &s) > -1 && (s.st_mode & S_IFBLK) ) + { + if( (path = (char *) malloc(strlen(dev)+1)) != NULL ) + { + strncpy(path, dev, strlen(dev)+1); + break; + } + } + } + } + } fclose(fp); - return 1; } - return 0; + return path; +} + +/* Check NVRAM staging file. */ +char * nvram_find_staging(void) +{ + struct stat s; + + if( (stat(NVRAM_STAGING, &s) > -1) && (s.st_mode & S_IFREG) ) + { + return NVRAM_STAGING; + } + + return NULL; +} + +/* Copy NVRAM contents to staging file. */ +int nvram_to_staging(void) +{ + int fdmtd, fdstg, stat; + char *mtd = nvram_find_mtd(); + char buf[nvram_erase_size]; + + stat = -1; + + if( (mtd != NULL) && (nvram_erase_size > 0) ) + { + if( (fdmtd = open(mtd, O_RDONLY)) > -1 ) + { + if( read(fdmtd, buf, sizeof(buf)) == sizeof(buf) ) + { + if((fdstg = open(NVRAM_STAGING, O_WRONLY | O_CREAT, 0600)) > -1) + { + write(fdstg, buf, sizeof(buf)); + fsync(fdstg); + close(fdstg); + + stat = 0; + } + } + + close(fdmtd); + } + } + + free(mtd); + return stat; +} + +/* Copy staging file to NVRAM device. */ +int staging_to_nvram(void) +{ + int fdmtd, fdstg, stat; + char *mtd = nvram_find_mtd(); + char buf[nvram_erase_size]; + + stat = -1; + + if( (mtd != NULL) && (nvram_erase_size > 0) ) + { + if( (fdstg = open(NVRAM_STAGING, O_RDONLY)) > -1 ) + { + if( read(fdstg, buf, sizeof(buf)) == sizeof(buf) ) + { + if( (fdmtd = open(mtd, O_WRONLY | O_SYNC)) > -1 ) + { + write(fdmtd, buf, sizeof(buf)); + fsync(fdmtd); + close(fdmtd); + stat = 0; + } + } + + close(fdstg); + + if( !stat ) + stat = unlink(NVRAM_STAGING) ? 1 : 0; + } + } + + free(mtd); + return stat; }