move a few packages to system/utils
[openwrt.git] / package / system / 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_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[h->offset];
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 i;
341         int fd;
342         char *mtd = NULL;
343         nvram_handle_t *h;
344         nvram_header_t *header;
345         int offset = -1;
346
347         /* If erase size or file are undefined then try to define them */
348         if( (nvram_erase_size == 0) || (file == NULL) )
349         {
350                 /* Finding the mtd will set the appropriate erase size */
351                 if( (mtd = nvram_find_mtd()) == NULL || nvram_erase_size == 0 )
352                 {
353                         free(mtd);
354                         return NULL;
355                 }
356         }
357
358         if( (fd = open(file ? file : mtd, O_RDWR)) > -1 )
359         {
360                 char *mmap_area = (char *) mmap(
361                         NULL, nvram_erase_size, PROT_READ | PROT_WRITE,
362                         (( rdonly == NVRAM_RO ) ? MAP_PRIVATE : MAP_SHARED) | MAP_LOCKED, fd, 0);
363
364                 if( mmap_area != MAP_FAILED )
365                 {
366                         for( i = 0; i <= ((nvram_erase_size - NVRAM_SPACE) / sizeof(uint32_t)); i++ )
367                         {
368                                 if( ((uint32_t *)mmap_area)[i] == NVRAM_MAGIC )
369                                 {
370                                         offset = i * sizeof(uint32_t);
371                                         break;
372                                 }
373                         }
374
375                         if( offset < 0 )
376                         {
377                                 free(mtd);
378                                 return NULL;
379                         }
380                         else if( (h = malloc(sizeof(nvram_handle_t))) != NULL )
381                         {
382                                 memset(h, 0, sizeof(nvram_handle_t));
383
384                                 h->fd     = fd;
385                                 h->mmap   = mmap_area;
386                                 h->length = nvram_erase_size;
387                                 h->offset = offset;
388
389                                 header = nvram_header(h);
390
391                                 if( header->magic == NVRAM_MAGIC )
392                                 {
393                                         _nvram_rehash(h);
394                                         free(mtd);
395                                         return h;
396                                 }
397                                 else
398                                 {
399                                         munmap(h->mmap, h->length);
400                                         free(h);
401                                 }
402                         }
403                 }
404         }
405
406         free(mtd);
407         return NULL;
408 }
409
410 /* Close NVRAM and free memory. */
411 int nvram_close(nvram_handle_t *h)
412 {
413         _nvram_free(h);
414         munmap(h->mmap, h->length);
415         close(h->fd);
416         free(h);
417
418         return 0;
419 }
420
421 /* Determine NVRAM device node. */
422 char * nvram_find_mtd(void)
423 {
424         FILE *fp;
425         int i, esz;
426         char dev[PATH_MAX];
427         char *path = NULL;
428         struct stat s;
429         int supported = 1;
430
431         /* Refuse any operation on the WGT634U */
432         if( (fp = fopen("/proc/diag/model", "r")) )
433         {
434                 if( fgets(dev, sizeof(dev), fp) && !strncmp(dev, "Netgear WGT634U", 15) )
435                         supported = 0;
436
437                 fclose(fp);
438         }
439
440         if( supported && (fp = fopen("/proc/mtd", "r")) )
441         {
442                 while( fgets(dev, sizeof(dev), fp) )
443                 {
444                         if( strstr(dev, "nvram") && sscanf(dev, "mtd%d: %08x", &i, &esz) )
445                         {
446                                 nvram_erase_size = esz;
447
448                                 sprintf(dev, "/dev/mtdblock/%d", i);
449                                 if( stat(dev, &s) > -1 && (s.st_mode & S_IFBLK) )
450                                 {
451                                         if( (path = (char *) malloc(strlen(dev)+1)) != NULL )
452                                         {
453                                                 strncpy(path, dev, strlen(dev)+1);
454                                                 break;
455                                         }
456                                 }
457                                 else
458                                 {
459                                         sprintf(dev, "/dev/mtdblock%d", i);
460                                         if( stat(dev, &s) > -1 && (s.st_mode & S_IFBLK) )
461                                         {
462                                                 if( (path = (char *) malloc(strlen(dev)+1)) != NULL )
463                                                 {
464                                                         strncpy(path, dev, strlen(dev)+1);
465                                                         break;
466                                                 }
467                                         }
468                                 }
469                         }
470                 }
471                 fclose(fp);
472         }
473
474         return path;
475 }
476
477 /* Check NVRAM staging file. */
478 char * nvram_find_staging(void)
479 {
480         struct stat s;
481
482         if( (stat(NVRAM_STAGING, &s) > -1) && (s.st_mode & S_IFREG) )
483         {
484                 return NVRAM_STAGING;
485         }
486
487         return NULL;
488 }
489
490 /* Copy NVRAM contents to staging file. */
491 int nvram_to_staging(void)
492 {
493         int fdmtd, fdstg, stat;
494         char *mtd = nvram_find_mtd();
495         char buf[nvram_erase_size];
496
497         stat = -1;
498
499         if( (mtd != NULL) && (nvram_erase_size > 0) )
500         {
501                 if( (fdmtd = open(mtd, O_RDONLY)) > -1 )
502                 {
503                         if( read(fdmtd, buf, sizeof(buf)) == sizeof(buf) )
504                         {
505                                 if((fdstg = open(NVRAM_STAGING, O_WRONLY | O_CREAT, 0600)) > -1)
506                                 {
507                                         write(fdstg, buf, sizeof(buf));
508                                         fsync(fdstg);
509                                         close(fdstg);
510
511                                         stat = 0;
512                                 }
513                         }
514
515                         close(fdmtd);
516                 }
517         }
518
519         free(mtd);
520         return stat;
521 }
522
523 /* Copy staging file to NVRAM device. */
524 int staging_to_nvram(void)
525 {
526         int fdmtd, fdstg, stat;
527         char *mtd = nvram_find_mtd();
528         char buf[nvram_erase_size];
529
530         stat = -1;
531
532         if( (mtd != NULL) && (nvram_erase_size > 0) )
533         {
534                 if( (fdstg = open(NVRAM_STAGING, O_RDONLY)) > -1 )
535                 {
536                         if( read(fdstg, buf, sizeof(buf)) == sizeof(buf) )
537                         {
538                                 if( (fdmtd = open(mtd, O_WRONLY | O_SYNC)) > -1 )
539                                 {
540                                         write(fdmtd, buf, sizeof(buf));
541                                         fsync(fdmtd);
542                                         close(fdmtd);
543                                         stat = 0;
544                                 }
545                         }
546
547                         close(fdstg);
548
549                         if( !stat )
550                                 stat = unlink(NVRAM_STAGING) ? 1 : 0;
551                 }
552         }
553
554         free(mtd);
555         return stat;
556 }