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