contrib/userspace-nvram:
[project/luci.git] / contrib / userspace-nvram / nvram.c
1 /*
2 * NVRAM variable manipulation (common)
3 *
4 * Copyright 2004, Broadcom Corporation
5 * Copyright 2009, 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_t *) &h->mmap[NVRAM_START(nvram_erase_size)];
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 the value of an NVRAM variable. */
143 char * nvram_get(nvram_handle_t *h, const char *name)
144 {
145 uint32_t i;
146 nvram_tuple_t *t;
147 char *value;
148
149 if (!name)
150 return NULL;
151
152 /* Hash the name */
153 i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash);
154
155 /* Find the associated tuple in the hash table */
156 for (t = h->nvram_hash[i]; t && strcmp(t->name, name); t = t->next);
157
158 value = t ? t->value : NULL;
159
160 return value;
161 }
162
163 /* Set the value of an NVRAM variable. */
164 int nvram_set(nvram_handle_t *h, const char *name, const char *value)
165 {
166 uint32_t i;
167 nvram_tuple_t *t, *u, **prev;
168
169 /* Hash the name */
170 i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash);
171
172 /* Find the associated tuple in the hash table */
173 for (prev = &h->nvram_hash[i], t = *prev;
174 t && strcmp(t->name, name); prev = &t->next, t = *prev);
175
176 /* (Re)allocate tuple */
177 if (!(u = _nvram_realloc(h, t, name, value)))
178 return -12; /* -ENOMEM */
179
180 /* Value reallocated */
181 if (t && t == u)
182 return 0;
183
184 /* Move old tuple to the dead table */
185 if (t) {
186 *prev = t->next;
187 t->next = h->nvram_dead;
188 h->nvram_dead = t;
189 }
190
191 /* Add new tuple to the hash table */
192 u->next = h->nvram_hash[i];
193 h->nvram_hash[i] = u;
194
195 return 0;
196 }
197
198 /* Unset the value of an NVRAM variable. */
199 int nvram_unset(nvram_handle_t *h, const char *name)
200 {
201 uint32_t i;
202 nvram_tuple_t *t, **prev;
203
204 if (!name)
205 return 0;
206
207 /* Hash the name */
208 i = hash(name) % NVRAM_ARRAYSIZE(h->nvram_hash);
209
210 /* Find the associated tuple in the hash table */
211 for (prev = &h->nvram_hash[i], t = *prev;
212 t && strcmp(t->name, name); prev = &t->next, t = *prev);
213
214 /* Move it to the dead table */
215 if (t) {
216 *prev = t->next;
217 t->next = h->nvram_dead;
218 h->nvram_dead = t;
219 }
220
221 return 0;
222 }
223
224 /* Get all NVRAM variables. */
225 nvram_tuple_t * nvram_getall(nvram_handle_t *h)
226 {
227 int i;
228 nvram_tuple_t *t, *l, *x;
229
230 l = NULL;
231
232 for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) {
233 for (t = h->nvram_hash[i]; t; t = t->next) {
234 if( (x = (nvram_tuple_t *) malloc(sizeof(nvram_tuple_t))) != NULL )
235 {
236 x->name = t->name;
237 x->value = t->value;
238 x->next = l;
239 l = x;
240 }
241 else
242 {
243 break;
244 }
245 }
246 }
247
248 return l;
249 }
250
251 /* Regenerate NVRAM. */
252 int nvram_commit(nvram_handle_t *h)
253 {
254 nvram_header_t *header = (nvram_header_t *) &h->mmap[NVRAM_START(nvram_erase_size)];
255 char *init, *config, *refresh, *ncdl;
256 char *ptr, *end;
257 int i;
258 nvram_tuple_t *t;
259 nvram_header_t tmp;
260 uint8_t crc;
261
262 /* Regenerate header */
263 header->magic = NVRAM_MAGIC;
264 header->crc_ver_init = (NVRAM_VERSION << 8);
265 if (!(init = nvram_get(h, "sdram_init")) ||
266 !(config = nvram_get(h, "sdram_config")) ||
267 !(refresh = nvram_get(h, "sdram_refresh")) ||
268 !(ncdl = nvram_get(h, "sdram_ncdl"))) {
269 header->crc_ver_init |= SDRAM_INIT << 16;
270 header->config_refresh = SDRAM_CONFIG;
271 header->config_refresh |= SDRAM_REFRESH << 16;
272 header->config_ncdl = 0;
273 } else {
274 header->crc_ver_init |= (strtoul(init, NULL, 0) & 0xffff) << 16;
275 header->config_refresh = strtoul(config, NULL, 0) & 0xffff;
276 header->config_refresh |= (strtoul(refresh, NULL, 0) & 0xffff) << 16;
277 header->config_ncdl = strtoul(ncdl, NULL, 0);
278 }
279
280 /* Clear data area */
281 ptr = (char *) header + sizeof(nvram_header_t);
282 memset(ptr, 0xFF, NVRAM_SPACE - sizeof(nvram_header_t));
283 memset(&tmp, 0, sizeof(nvram_header_t));
284
285 /* Leave space for a double NUL at the end */
286 end = (char *) header + NVRAM_SPACE - 2;
287
288 /* Write out all tuples */
289 for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) {
290 for (t = h->nvram_hash[i]; t; t = t->next) {
291 if ((ptr + strlen(t->name) + 1 + strlen(t->value) + 1) > end)
292 break;
293 ptr += sprintf(ptr, "%s=%s", t->name, t->value) + 1;
294 }
295 }
296
297 /* End with a double NUL */
298 *ptr = '\0';
299 ptr += 2;
300
301 /* Set new length */
302 header->len = NVRAM_ROUNDUP(ptr - (char *) header, 4);
303
304 /* Little-endian CRC8 over the last 11 bytes of the header */
305 tmp.crc_ver_init = htonl(header->crc_ver_init);
306 tmp.config_refresh = htonl(header->config_refresh);
307 tmp.config_ncdl = htonl(header->config_ncdl);
308 crc = hndcrc8((unsigned char *) &tmp + 9, sizeof(nvram_header_t) - 9, 0xff);
309
310 /* Continue CRC8 over data bytes */
311 crc = hndcrc8((unsigned char *) &header[1],
312 header->len - sizeof(nvram_header_t), crc);
313
314 /* Set new CRC8 */
315 header->crc_ver_init |= crc;
316
317 /* Write out */
318 msync(h->mmap, h->length, MS_SYNC);
319 fsync(h->fd);
320
321 /* Reinitialize hash table */
322 return _nvram_rehash(h);
323 }
324
325 /* Open NVRAM and obtain a handle. */
326 nvram_handle_t * nvram_open(const char *file, int rdonly)
327 {
328 int fd;
329 nvram_handle_t *h;
330 nvram_header_t *header;
331
332 /* If erase size or file are undefined then try to define them */
333 if( (nvram_erase_size == 0) || (file == NULL) )
334 {
335 /* Finding the mtd will set the appropriate erase size */
336 if( file == NULL )
337 file = nvram_find_mtd();
338 else
339 (void) nvram_find_mtd();
340
341 if( nvram_erase_size == 0 )
342 return NULL;
343 }
344
345 if( (fd = open(file, O_RDWR)) > -1 )
346 {
347 char *mmap_area = (char *) mmap(
348 NULL, nvram_erase_size, PROT_READ | PROT_WRITE,
349 ( rdonly == NVRAM_RO ) ? MAP_PRIVATE : MAP_SHARED, fd, 0);
350
351 if( mmap_area != MAP_FAILED )
352 {
353 memset(mmap_area, 0xFF, NVRAM_START(nvram_erase_size));
354
355 if((h = (nvram_handle_t *) malloc(sizeof(nvram_handle_t))) != NULL)
356 {
357 memset(h, 0, sizeof(nvram_handle_t));
358
359 h->fd = fd;
360 h->mmap = mmap_area;
361 h->length = nvram_erase_size;
362
363 header = (nvram_header_t *) &h->mmap[NVRAM_START(nvram_erase_size)];
364
365 if( header->magic == NVRAM_MAGIC )
366 {
367 _nvram_rehash(h);
368 return h;
369 }
370 else
371 {
372 munmap(h->mmap, h->length);
373 free(h);
374 }
375 }
376 }
377 }
378
379 return NULL;
380 }
381
382 /* Close NVRAM and free memory. */
383 int nvram_close(nvram_handle_t *h)
384 {
385 _nvram_free(h);
386 munmap(h->mmap, h->length);
387 close(h->fd);
388 free(h);
389
390 return 0;
391 }
392
393 /* Determine NVRAM device node. */
394 const char * nvram_find_mtd(void)
395 {
396 FILE *fp;
397 int i, esz;
398 char dev[PATH_MAX];
399 char *path = NULL;
400
401 // "/dev/mtdblock/" + ( 0 < x < 99 ) + \0 = 19
402 if( (path = (char *) malloc(19)) == NULL )
403 return NULL;
404
405 if ((fp = fopen("/proc/mtd", "r"))) {
406 while (fgets(dev, sizeof(dev), fp)) {
407 if (strstr(dev, "nvram") && sscanf(dev, "mtd%d: %08x", &i, &esz)) {
408 if( (path = (char *) malloc(19)) != NULL )
409 {
410 nvram_erase_size = esz;
411 snprintf(path, 19, "/dev/mtdblock/%d", i);
412 break;
413 }
414 }
415 }
416 fclose(fp);
417 }
418
419 return path;
420 }
421
422 /* Check NVRAM staging file. */
423 const char * nvram_find_staging(void)
424 {
425 struct stat s;
426
427 if( (stat(NVRAM_STAGING, &s) > -1) && (s.st_mode & S_IFREG) )
428 {
429 return NVRAM_STAGING;
430 }
431
432 return NULL;
433 }
434
435 /* Copy NVRAM contents to staging file. */
436 int nvram_to_staging(void)
437 {
438 int fdmtd, fdstg, stat;
439 const char *mtd = nvram_find_mtd();
440 char buf[nvram_erase_size];
441
442 stat = -1;
443
444 if( (mtd != NULL) && (nvram_erase_size > 0) )
445 {
446 if( (fdmtd = open(mtd, O_RDONLY)) > -1 )
447 {
448 if( read(fdmtd, buf, sizeof(buf)) == sizeof(buf) )
449 {
450 if((fdstg = open(NVRAM_STAGING, O_WRONLY | O_CREAT, 0600)) > -1)
451 {
452 write(fdstg, buf, sizeof(buf));
453 fsync(fdstg);
454 close(fdstg);
455
456 stat = 0;
457 }
458 }
459
460 close(fdmtd);
461 }
462 }
463
464 return stat;
465 }
466
467 /* Copy staging file to NVRAM device. */
468 int staging_to_nvram(void)
469 {
470 int fdmtd, fdstg, stat;
471 const char *mtd = nvram_find_mtd();
472 char buf[nvram_erase_size];
473
474 stat = -1;
475
476 if( (mtd != NULL) && (nvram_erase_size > 0) )
477 {
478 if( (fdstg = open(NVRAM_STAGING, O_RDONLY)) > -1 )
479 {
480 if( read(fdstg, buf, sizeof(buf)) == sizeof(buf) )
481 {
482 if( (fdmtd = open(mtd, O_WRONLY | O_SYNC)) > -1 )
483 {
484 write(fdmtd, buf, sizeof(buf));
485 fsync(fdmtd);
486 close(fdmtd);
487 stat = 0;
488 }
489 }
490
491 close(fdstg);
492
493 if( !stat )
494 stat = unlink(NVRAM_STAGING) ? 1 : 0;
495 }
496 }
497
498 return stat;
499 }