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