| 1 | /* |
| 2 | * NVRAM variable manipulation (common) |
| 3 | * |
| 4 | * Copyright 2004, Broadcom Corporation |
| 5 | * Copyright 2009-2010, OpenWrt.org |
| 6 | * All Rights Reserved. |
| 7 | * |
| 8 | * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY |
| 9 | * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM |
| 10 | * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS |
| 11 | * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. |
| 12 | * |
| 13 | */ |
| 14 | |
| 15 | #include "nvram.h" |
| 16 | |
| 17 | #define TRACE(msg) \ |
| 18 | printf("%s(%i) in %s(): %s\n", \ |
| 19 | __FILE__, __LINE__, __FUNCTION__, msg ? msg : "?") |
| 20 | |
| 21 | size_t nvram_erase_size = 0; |
| 22 | |
| 23 | |
| 24 | /* |
| 25 | * -- Helper functions -- |
| 26 | */ |
| 27 | |
| 28 | /* String hash */ |
| 29 | static uint32_t hash(const char *s) |
| 30 | { |
| 31 | uint32_t hash = 0; |
| 32 | |
| 33 | while (*s) |
| 34 | hash = 31 * hash + *s++; |
| 35 | |
| 36 | return hash; |
| 37 | } |
| 38 | |
| 39 | /* Free all tuples. */ |
| 40 | static void _nvram_free(nvram_handle_t *h) |
| 41 | { |
| 42 | uint32_t i; |
| 43 | nvram_tuple_t *t, *next; |
| 44 | |
| 45 | /* Free hash table */ |
| 46 | for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) { |
| 47 | for (t = h->nvram_hash[i]; t; t = next) { |
| 48 | next = t->next; |
| 49 | free(t); |
| 50 | } |
| 51 | h->nvram_hash[i] = NULL; |
| 52 | } |
| 53 | |
| 54 | /* Free dead table */ |
| 55 | for (t = h->nvram_dead; t; t = next) { |
| 56 | next = t->next; |
| 57 | free(t); |
| 58 | } |
| 59 | |
| 60 | h->nvram_dead = NULL; |
| 61 | } |
| 62 | |
| 63 | /* (Re)allocate NVRAM tuples. */ |
| 64 | static nvram_tuple_t * _nvram_realloc( nvram_handle_t *h, nvram_tuple_t *t, |
| 65 | const char *name, const char *value ) |
| 66 | { |
| 67 | if ((strlen(value) + 1) > NVRAM_SPACE) |
| 68 | return NULL; |
| 69 | |
| 70 | if (!t) { |
| 71 | if (!(t = malloc(sizeof(nvram_tuple_t) + strlen(name) + 1))) |
| 72 | return NULL; |
| 73 | |
| 74 | /* Copy name */ |
| 75 | t->name = (char *) &t[1]; |
| 76 | strcpy(t->name, name); |
| 77 | |
| 78 | t->value = NULL; |
| 79 | } |
| 80 | |
| 81 | /* Copy value */ |
| 82 | if (!t->value || strcmp(t->value, value)) |
| 83 | { |
| 84 | if(!(t->value = (char *) realloc(t->value, strlen(value)+1))) |
| 85 | return NULL; |
| 86 | |
| 87 | strcpy(t->value, value); |
| 88 | t->value[strlen(value)] = '\0'; |
| 89 | } |
| 90 | |
| 91 | return t; |
| 92 | } |
| 93 | |
| 94 | /* (Re)initialize the hash table. */ |
| 95 | static int _nvram_rehash(nvram_handle_t *h) |
| 96 | { |
| 97 | nvram_header_t *header = nvram_header(h); |
| 98 | char buf[] = "0xXXXXXXXX", *name, *value, *eq; |
| 99 | |
| 100 | /* (Re)initialize hash table */ |
| 101 | _nvram_free(h); |
| 102 | |
| 103 | /* Parse and set "name=value\0 ... \0\0" */ |
| 104 | name = (char *) &header[1]; |
| 105 | |
| 106 | for (; *name; name = value + strlen(value) + 1) { |
| 107 | if (!(eq = strchr(name, '='))) |
| 108 | break; |
| 109 | *eq = '\0'; |
| 110 | value = eq + 1; |
| 111 | nvram_set(h, name, value); |
| 112 | *eq = '='; |
| 113 | } |
| 114 | |
| 115 | /* Set special SDRAM parameters */ |
| 116 | if (!nvram_get(h, "sdram_init")) { |
| 117 | sprintf(buf, "0x%04X", (uint16_t)(header->crc_ver_init >> 16)); |
| 118 | nvram_set(h, "sdram_init", buf); |
| 119 | } |
| 120 | if (!nvram_get(h, "sdram_config")) { |
| 121 | sprintf(buf, "0x%04X", (uint16_t)(header->config_refresh & 0xffff)); |
| 122 | nvram_set(h, "sdram_config", buf); |
| 123 | } |
| 124 | if (!nvram_get(h, "sdram_refresh")) { |
| 125 | sprintf(buf, "0x%04X", |
| 126 | (uint16_t)((header->config_refresh >> 16) & 0xffff)); |
| 127 | nvram_set(h, "sdram_refresh", buf); |
| 128 | } |
| 129 | if (!nvram_get(h, "sdram_ncdl")) { |
| 130 | sprintf(buf, "0x%08X", header->config_ncdl); |
| 131 | nvram_set(h, "sdram_ncdl", buf); |
| 132 | } |
| 133 | |
| 134 | return 0; |
| 135 | } |
| 136 | |
| 137 | |
| 138 | /* |
| 139 | * -- Public functions -- |
| 140 | */ |
| 141 | |
| 142 | /* Get nvram header. */ |
| 143 | nvram_header_t * nvram_header(nvram_handle_t *h) |
| 144 | { |
| 145 | return (nvram_header_t *) &h->mmap[NVRAM_START(nvram_erase_size)]; |
| 146 | } |
| 147 | |
| 148 | /* Get the value of an NVRAM variable. */ |
| 149 | char * nvram_get(nvram_handle_t *h, const char *name) |
| 150 | { |
| 151 | uint32_t i; |
| 152 | nvram_tuple_t *t; |
| 153 | char *value; |
| 154 | |
| 155 | if (!name) |
| 156 | return NULL; |
| 157 | |
| 158 | /* Hash the name */ |
| 159 | i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash); |
| 160 | |
| 161 | /* Find the associated tuple in the hash table */ |
| 162 | for (t = h->nvram_hash[i]; t && strcmp(t->name, name); t = t->next); |
| 163 | |
| 164 | value = t ? t->value : NULL; |
| 165 | |
| 166 | return value; |
| 167 | } |
| 168 | |
| 169 | /* Set the value of an NVRAM variable. */ |
| 170 | int nvram_set(nvram_handle_t *h, const char *name, const char *value) |
| 171 | { |
| 172 | uint32_t i; |
| 173 | nvram_tuple_t *t, *u, **prev; |
| 174 | |
| 175 | /* Hash the name */ |
| 176 | i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash); |
| 177 | |
| 178 | /* Find the associated tuple in the hash table */ |
| 179 | for (prev = &h->nvram_hash[i], t = *prev; |
| 180 | t && strcmp(t->name, name); prev = &t->next, t = *prev); |
| 181 | |
| 182 | /* (Re)allocate tuple */ |
| 183 | if (!(u = _nvram_realloc(h, t, name, value))) |
| 184 | return -12; /* -ENOMEM */ |
| 185 | |
| 186 | /* Value reallocated */ |
| 187 | if (t && t == u) |
| 188 | return 0; |
| 189 | |
| 190 | /* Move old tuple to the dead table */ |
| 191 | if (t) { |
| 192 | *prev = t->next; |
| 193 | t->next = h->nvram_dead; |
| 194 | h->nvram_dead = t; |
| 195 | } |
| 196 | |
| 197 | /* Add new tuple to the hash table */ |
| 198 | u->next = h->nvram_hash[i]; |
| 199 | h->nvram_hash[i] = u; |
| 200 | |
| 201 | return 0; |
| 202 | } |
| 203 | |
| 204 | /* Unset the value of an NVRAM variable. */ |
| 205 | int nvram_unset(nvram_handle_t *h, const char *name) |
| 206 | { |
| 207 | uint32_t i; |
| 208 | nvram_tuple_t *t, **prev; |
| 209 | |
| 210 | if (!name) |
| 211 | return 0; |
| 212 | |
| 213 | /* Hash the name */ |
| 214 | i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash); |
| 215 | |
| 216 | /* Find the associated tuple in the hash table */ |
| 217 | for (prev = &h->nvram_hash[i], t = *prev; |
| 218 | t && strcmp(t->name, name); prev = &t->next, t = *prev); |
| 219 | |
| 220 | /* Move it to the dead table */ |
| 221 | if (t) { |
| 222 | *prev = t->next; |
| 223 | t->next = h->nvram_dead; |
| 224 | h->nvram_dead = t; |
| 225 | } |
| 226 | |
| 227 | return 0; |
| 228 | } |
| 229 | |
| 230 | /* Get all NVRAM variables. */ |
| 231 | nvram_tuple_t * nvram_getall(nvram_handle_t *h) |
| 232 | { |
| 233 | int i; |
| 234 | nvram_tuple_t *t, *l, *x; |
| 235 | |
| 236 | l = NULL; |
| 237 | |
| 238 | for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) { |
| 239 | for (t = h->nvram_hash[i]; t; t = t->next) { |
| 240 | if( (x = (nvram_tuple_t *) malloc(sizeof(nvram_tuple_t))) != NULL ) |
| 241 | { |
| 242 | x->name = t->name; |
| 243 | x->value = t->value; |
| 244 | x->next = l; |
| 245 | l = x; |
| 246 | } |
| 247 | else |
| 248 | { |
| 249 | break; |
| 250 | } |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | return l; |
| 255 | } |
| 256 | |
| 257 | /* Regenerate NVRAM. */ |
| 258 | int nvram_commit(nvram_handle_t *h) |
| 259 | { |
| 260 | nvram_header_t *header = nvram_header(h); |
| 261 | char *init, *config, *refresh, *ncdl; |
| 262 | char *ptr, *end; |
| 263 | int i; |
| 264 | nvram_tuple_t *t; |
| 265 | nvram_header_t tmp; |
| 266 | uint8_t crc; |
| 267 | |
| 268 | /* Regenerate header */ |
| 269 | header->magic = NVRAM_MAGIC; |
| 270 | header->crc_ver_init = (NVRAM_VERSION << 8); |
| 271 | if (!(init = nvram_get(h, "sdram_init")) || |
| 272 | !(config = nvram_get(h, "sdram_config")) || |
| 273 | !(refresh = nvram_get(h, "sdram_refresh")) || |
| 274 | !(ncdl = nvram_get(h, "sdram_ncdl"))) { |
| 275 | header->crc_ver_init |= SDRAM_INIT << 16; |
| 276 | header->config_refresh = SDRAM_CONFIG; |
| 277 | header->config_refresh |= SDRAM_REFRESH << 16; |
| 278 | header->config_ncdl = 0; |
| 279 | } else { |
| 280 | header->crc_ver_init |= (strtoul(init, NULL, 0) & 0xffff) << 16; |
| 281 | header->config_refresh = strtoul(config, NULL, 0) & 0xffff; |
| 282 | header->config_refresh |= (strtoul(refresh, NULL, 0) & 0xffff) << 16; |
| 283 | header->config_ncdl = strtoul(ncdl, NULL, 0); |
| 284 | } |
| 285 | |
| 286 | /* Clear data area */ |
| 287 | ptr = (char *) header + sizeof(nvram_header_t); |
| 288 | memset(ptr, 0xFF, NVRAM_SPACE - sizeof(nvram_header_t)); |
| 289 | memset(&tmp, 0, sizeof(nvram_header_t)); |
| 290 | |
| 291 | /* Leave space for a double NUL at the end */ |
| 292 | end = (char *) header + NVRAM_SPACE - 2; |
| 293 | |
| 294 | /* Write out all tuples */ |
| 295 | for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) { |
| 296 | for (t = h->nvram_hash[i]; t; t = t->next) { |
| 297 | if ((ptr + strlen(t->name) + 1 + strlen(t->value) + 1) > end) |
| 298 | break; |
| 299 | ptr += sprintf(ptr, "%s=%s", t->name, t->value) + 1; |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | /* End with a double NULL and pad to 4 bytes */ |
| 304 | *ptr = '\0'; |
| 305 | ptr++; |
| 306 | |
| 307 | if( (int)ptr % 4 ) |
| 308 | memset(ptr, 0, 4 - ((int)ptr % 4)); |
| 309 | |
| 310 | ptr++; |
| 311 | |
| 312 | /* Set new length */ |
| 313 | header->len = NVRAM_ROUNDUP(ptr - (char *) header, 4); |
| 314 | |
| 315 | /* Little-endian CRC8 over the last 11 bytes of the header */ |
| 316 | tmp.crc_ver_init = header->crc_ver_init; |
| 317 | tmp.config_refresh = header->config_refresh; |
| 318 | tmp.config_ncdl = header->config_ncdl; |
| 319 | crc = hndcrc8((unsigned char *) &tmp + NVRAM_CRC_START_POSITION, |
| 320 | sizeof(nvram_header_t) - NVRAM_CRC_START_POSITION, 0xff); |
| 321 | |
| 322 | /* Continue CRC8 over data bytes */ |
| 323 | crc = hndcrc8((unsigned char *) &header[0] + sizeof(nvram_header_t), |
| 324 | header->len - sizeof(nvram_header_t), crc); |
| 325 | |
| 326 | /* Set new CRC8 */ |
| 327 | header->crc_ver_init |= crc; |
| 328 | |
| 329 | /* Write out */ |
| 330 | msync(h->mmap, h->length, MS_SYNC); |
| 331 | fsync(h->fd); |
| 332 | |
| 333 | /* Reinitialize hash table */ |
| 334 | return _nvram_rehash(h); |
| 335 | } |
| 336 | |
| 337 | /* Open NVRAM and obtain a handle. */ |
| 338 | nvram_handle_t * nvram_open(const char *file, int rdonly) |
| 339 | { |
| 340 | int fd; |
| 341 | char *mtd = NULL; |
| 342 | nvram_handle_t *h; |
| 343 | nvram_header_t *header; |
| 344 | |
| 345 | /* If erase size or file are undefined then try to define them */ |
| 346 | if( (nvram_erase_size == 0) || (file == NULL) ) |
| 347 | { |
| 348 | /* Finding the mtd will set the appropriate erase size */ |
| 349 | if( (mtd = nvram_find_mtd()) == NULL || nvram_erase_size == 0 ) |
| 350 | { |
| 351 | free(mtd); |
| 352 | return NULL; |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | if( (fd = open(file ? file : mtd, O_RDWR)) > -1 ) |
| 357 | { |
| 358 | char *mmap_area = (char *) mmap( |
| 359 | NULL, nvram_erase_size, PROT_READ | PROT_WRITE, |
| 360 | (( rdonly == NVRAM_RO ) ? MAP_PRIVATE : MAP_SHARED) | MAP_LOCKED, fd, 0); |
| 361 | |
| 362 | if( mmap_area != MAP_FAILED ) |
| 363 | { |
| 364 | memset(mmap_area, 0xFF, NVRAM_START(nvram_erase_size)); |
| 365 | |
| 366 | if((h = (nvram_handle_t *) malloc(sizeof(nvram_handle_t))) != NULL) |
| 367 | { |
| 368 | memset(h, 0, sizeof(nvram_handle_t)); |
| 369 | |
| 370 | h->fd = fd; |
| 371 | h->mmap = mmap_area; |
| 372 | h->length = nvram_erase_size; |
| 373 | |
| 374 | header = nvram_header(h); |
| 375 | |
| 376 | if( header->magic == NVRAM_MAGIC ) |
| 377 | { |
| 378 | _nvram_rehash(h); |
| 379 | free(mtd); |
| 380 | return h; |
| 381 | } |
| 382 | else |
| 383 | { |
| 384 | munmap(h->mmap, h->length); |
| 385 | free(h); |
| 386 | } |
| 387 | } |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | free(mtd); |
| 392 | return NULL; |
| 393 | } |
| 394 | |
| 395 | /* Close NVRAM and free memory. */ |
| 396 | int nvram_close(nvram_handle_t *h) |
| 397 | { |
| 398 | _nvram_free(h); |
| 399 | munmap(h->mmap, h->length); |
| 400 | close(h->fd); |
| 401 | free(h); |
| 402 | |
| 403 | return 0; |
| 404 | } |
| 405 | |
| 406 | /* Determine NVRAM device node. */ |
| 407 | char * nvram_find_mtd(void) |
| 408 | { |
| 409 | FILE *fp; |
| 410 | int i, esz; |
| 411 | char dev[PATH_MAX]; |
| 412 | char *path = NULL; |
| 413 | struct stat s; |
| 414 | int supported = 1; |
| 415 | |
| 416 | /* Refuse any operation on the WGT634U */ |
| 417 | if( (fp = fopen("/proc/diag/model", "r")) ) |
| 418 | { |
| 419 | if( fgets(dev, sizeof(dev), fp) && !strncmp(dev, "Netgear WGT634U", 15) ) |
| 420 | supported = 0; |
| 421 | |
| 422 | fclose(fp); |
| 423 | } |
| 424 | |
| 425 | if( supported && (fp = fopen("/proc/mtd", "r")) ) |
| 426 | { |
| 427 | while( fgets(dev, sizeof(dev), fp) ) |
| 428 | { |
| 429 | if( strstr(dev, "nvram") && sscanf(dev, "mtd%d: %08x", &i, &esz) ) |
| 430 | { |
| 431 | nvram_erase_size = esz; |
| 432 | |
| 433 | sprintf(dev, "/dev/mtdblock/%d", i); |
| 434 | if( stat(dev, &s) > -1 && (s.st_mode & S_IFBLK) ) |
| 435 | { |
| 436 | if( (path = (char *) malloc(strlen(dev)+1)) != NULL ) |
| 437 | { |
| 438 | strncpy(path, dev, strlen(dev)+1); |
| 439 | break; |
| 440 | } |
| 441 | } |
| 442 | else |
| 443 | { |
| 444 | sprintf(dev, "/dev/mtdblock%d", i); |
| 445 | if( stat(dev, &s) > -1 && (s.st_mode & S_IFBLK) ) |
| 446 | { |
| 447 | if( (path = (char *) malloc(strlen(dev)+1)) != NULL ) |
| 448 | { |
| 449 | strncpy(path, dev, strlen(dev)+1); |
| 450 | break; |
| 451 | } |
| 452 | } |
| 453 | } |
| 454 | } |
| 455 | } |
| 456 | fclose(fp); |
| 457 | } |
| 458 | |
| 459 | return path; |
| 460 | } |
| 461 | |
| 462 | /* Check NVRAM staging file. */ |
| 463 | char * nvram_find_staging(void) |
| 464 | { |
| 465 | struct stat s; |
| 466 | |
| 467 | if( (stat(NVRAM_STAGING, &s) > -1) && (s.st_mode & S_IFREG) ) |
| 468 | { |
| 469 | return NVRAM_STAGING; |
| 470 | } |
| 471 | |
| 472 | return NULL; |
| 473 | } |
| 474 | |
| 475 | /* Copy NVRAM contents to staging file. */ |
| 476 | int nvram_to_staging(void) |
| 477 | { |
| 478 | int fdmtd, fdstg, stat; |
| 479 | char *mtd = nvram_find_mtd(); |
| 480 | char buf[nvram_erase_size]; |
| 481 | |
| 482 | stat = -1; |
| 483 | |
| 484 | if( (mtd != NULL) && (nvram_erase_size > 0) ) |
| 485 | { |
| 486 | if( (fdmtd = open(mtd, O_RDONLY)) > -1 ) |
| 487 | { |
| 488 | if( read(fdmtd, buf, sizeof(buf)) == sizeof(buf) ) |
| 489 | { |
| 490 | if((fdstg = open(NVRAM_STAGING, O_WRONLY | O_CREAT, 0600)) > -1) |
| 491 | { |
| 492 | write(fdstg, buf, sizeof(buf)); |
| 493 | fsync(fdstg); |
| 494 | close(fdstg); |
| 495 | |
| 496 | stat = 0; |
| 497 | } |
| 498 | } |
| 499 | |
| 500 | close(fdmtd); |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | free(mtd); |
| 505 | return stat; |
| 506 | } |
| 507 | |
| 508 | /* Copy staging file to NVRAM device. */ |
| 509 | int staging_to_nvram(void) |
| 510 | { |
| 511 | int fdmtd, fdstg, stat; |
| 512 | char *mtd = nvram_find_mtd(); |
| 513 | char buf[nvram_erase_size]; |
| 514 | |
| 515 | stat = -1; |
| 516 | |
| 517 | if( (mtd != NULL) && (nvram_erase_size > 0) ) |
| 518 | { |
| 519 | if( (fdstg = open(NVRAM_STAGING, O_RDONLY)) > -1 ) |
| 520 | { |
| 521 | if( read(fdstg, buf, sizeof(buf)) == sizeof(buf) ) |
| 522 | { |
| 523 | if( (fdmtd = open(mtd, O_WRONLY | O_SYNC)) > -1 ) |
| 524 | { |
| 525 | write(fdmtd, buf, sizeof(buf)); |
| 526 | fsync(fdmtd); |
| 527 | close(fdmtd); |
| 528 | stat = 0; |
| 529 | } |
| 530 | } |
| 531 | |
| 532 | close(fdstg); |
| 533 | |
| 534 | if( !stat ) |
| 535 | stat = unlink(NVRAM_STAGING) ? 1 : 0; |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | free(mtd); |
| 540 | return stat; |
| 541 | } |
| 542 | |