| /* |
| * NVRAM variable manipulation (common) |
| * |
| * Copyright 2004, Broadcom Corporation |
| * Copyright 2009-2010, 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 |
| * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. |
| * |
| */ |
| |
| #include "nvram.h" |
| |
| #define TRACE(msg) \ |
| printf("%s(%i) in %s(): %s\n", \ |
| __FILE__, __LINE__, __FUNCTION__, msg ? msg : "?") |
| |
| /* Size of "nvram" MTD partition */ |
| size_t nvram_part_size = 0; |
| |
| |
| /* |
| * -- Helper functions -- |
| */ |
| |
| /* String hash */ |
| static uint32_t hash(const char *s) |
| { |
| uint32_t hash = 0; |
| |
| while (*s) |
| hash = 31 * hash + *s++; |
| |
| 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; |
| if (t->value) |
| free(t->value); |
| free(t); |
| } |
| h->nvram_hash[i] = NULL; |
| } |
| |
| /* Free dead table */ |
| for (t = h->nvram_dead; t; t = next) { |
| next = t->next; |
| if (t->value) |
| free(t->value); |
| free(t); |
| } |
| |
| h->nvram_dead = NULL; |
| } |
| |
| /* (Re)allocate NVRAM tuples. */ |
| static nvram_tuple_t * _nvram_realloc( nvram_handle_t *h, nvram_tuple_t *t, |
| const char *name, const char *value ) |
| { |
| if ((strlen(value) + 1) > h->length - h->offset) |
| return NULL; |
| |
| if (!t) { |
| if (!(t = malloc(sizeof(nvram_tuple_t) + strlen(name) + 1))) |
| return NULL; |
| |
| /* 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); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* |
| * -- Public functions -- |
| */ |
| |
| /* Get nvram header. */ |
| nvram_header_t * nvram_header(nvram_handle_t *h) |
| { |
| return (nvram_header_t *) &h->mmap[h->offset]; |
| } |
| |
| /* 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 (!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; |
| } |
| |
| /* Set the value of an NVRAM variable. */ |
| int nvram_set(nvram_handle_t *h, const char *name, const char *value) |
| { |
| 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; |
| } |
| |
| /* Add new tuple to the hash table */ |
| u->next = h->nvram_hash[i]; |
| h->nvram_hash[i] = u; |
| |
| 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; |
| |
| /* 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); |
| |
| /* Move it to the dead table */ |
| if (t) { |
| *prev = t->next; |
| t->next = h->nvram_dead; |
| h->nvram_dead = t; |
| } |
| |
| return 0; |
| } |
| |
| /* Get all NVRAM variables. */ |
| nvram_tuple_t * nvram_getall(nvram_handle_t *h) |
| { |
| 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; |
| } |
| } |
| } |
| |
| return l; |
| } |
| |
| /* 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); |
| } |
| |
| /* Clear data area */ |
| ptr = (char *) header + sizeof(nvram_header_t); |
| memset(ptr, 0xFF, nvram_part_size - h->offset - sizeof(nvram_header_t)); |
| memset(&tmp, 0, sizeof(nvram_header_t)); |
| |
| /* Leave space for a double NUL at the end */ |
| end = (char *) header + nvram_part_size - h->offset - 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; |
| } |
| } |
| |
| /* End with a double NULL and pad to 4 bytes */ |
| *ptr = '\0'; |
| ptr++; |
| |
| if( (int)ptr % 4 ) |
| memset(ptr, 0, 4 - ((int)ptr % 4)); |
| |
| ptr++; |
| |
| /* Set new length */ |
| header->len = NVRAM_ROUNDUP(ptr - (char *) header, 4); |
| |
| /* 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); |
| |
| /* 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); |
| } |
| |
| /* Open NVRAM and obtain a handle. */ |
| nvram_handle_t * nvram_open(const char *file, int rdonly) |
| { |
| int i; |
| int fd; |
| char *mtd = NULL; |
| nvram_handle_t *h; |
| nvram_header_t *header; |
| int offset = -1; |
| |
| /* If erase size or file are undefined then try to define them */ |
| if( (nvram_part_size == 0) || (file == NULL) ) |
| { |
| /* Finding the mtd will set the appropriate erase size */ |
| if( (mtd = nvram_find_mtd()) == NULL || nvram_part_size == 0 ) |
| { |
| free(mtd); |
| return NULL; |
| } |
| } |
| |
| if( (fd = open(file ? file : mtd, O_RDWR)) > -1 ) |
| { |
| char *mmap_area = (char *) mmap( |
| NULL, nvram_part_size, PROT_READ | PROT_WRITE, |
| (( rdonly == NVRAM_RO ) ? MAP_PRIVATE : MAP_SHARED) | MAP_LOCKED, fd, 0); |
| |
| if( mmap_area != MAP_FAILED ) |
| { |
| /* |
| * Start looking for NVRAM_MAGIC at beginning of MTD |
| * partition. Stop if there is less than NVRAM_MIN_SPACE |
| * to check, that was the lowest used size. |
| */ |
| for( i = 0; i <= ((nvram_part_size - NVRAM_MIN_SPACE) / sizeof(uint32_t)); i++ ) |
| { |
| if( ((uint32_t *)mmap_area)[i] == NVRAM_MAGIC ) |
| { |
| offset = i * sizeof(uint32_t); |
| break; |
| } |
| } |
| |
| if( offset < 0 ) |
| { |
| munmap(mmap_area, nvram_part_size); |
| free(mtd); |
| close(fd); |
| return NULL; |
| } |
| else if( (h = malloc(sizeof(nvram_handle_t))) != NULL ) |
| { |
| memset(h, 0, sizeof(nvram_handle_t)); |
| |
| h->fd = fd; |
| h->mmap = mmap_area; |
| h->length = nvram_part_size; |
| h->offset = offset; |
| |
| header = nvram_header(h); |
| |
| if (header->magic == NVRAM_MAGIC && |
| (rdonly || header->len < h->length - h->offset)) { |
| _nvram_rehash(h); |
| free(mtd); |
| return h; |
| } |
| else |
| { |
| munmap(h->mmap, h->length); |
| free(h); |
| } |
| } |
| } |
| } |
| |
| free(mtd); |
| close(fd); |
| return NULL; |
| } |
| |
| /* 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 0; |
| } |
| |
| /* Determine NVRAM device node. */ |
| char * nvram_find_mtd(void) |
| { |
| FILE *fp; |
| int i, part_size; |
| char dev[PATH_MAX]; |
| char *path = NULL; |
| struct stat s; |
| |
| if ((fp = fopen("/proc/mtd", "r"))) |
| { |
| while( fgets(dev, sizeof(dev), fp) ) |
| { |
| if( strstr(dev, "nvram") && sscanf(dev, "mtd%d: %08x", &i, &part_size) ) |
| { |
| nvram_part_size = part_size; |
| |
| 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 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_part_size]; |
| |
| stat = -1; |
| |
| if( (mtd != NULL) && (nvram_part_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_part_size]; |
| |
| stat = -1; |
| |
| if( (mtd != NULL) && (nvram_part_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; |
| } |