0e4294391ad69af92d2682ac144518fa20b6e9a5
[15.05/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                                 free(mtd);
384                                 return NULL;
385                         }
386                         else if( (h = malloc(sizeof(nvram_handle_t))) != NULL )
387                         {
388                                 memset(h, 0, sizeof(nvram_handle_t));
389
390                                 h->fd     = fd;
391                                 h->mmap   = mmap_area;
392                                 h->length = nvram_part_size;
393                                 h->offset = offset;
394
395                                 header = nvram_header(h);
396
397                                 if (header->magic == NVRAM_MAGIC &&
398                                     (rdonly || header->len < h->length - h->offset)) {
399                                         _nvram_rehash(h);
400                                         free(mtd);
401                                         return h;
402                                 }
403                                 else
404                                 {
405                                         munmap(h->mmap, h->length);
406                                         free(h);
407                                 }
408                         }
409                 }
410         }
411
412         free(mtd);
413         return NULL;
414 }
415
416 /* Close NVRAM and free memory. */
417 int nvram_close(nvram_handle_t *h)
418 {
419         _nvram_free(h);
420         munmap(h->mmap, h->length);
421         close(h->fd);
422         free(h);
423
424         return 0;
425 }
426
427 /* Determine NVRAM device node. */
428 char * nvram_find_mtd(void)
429 {
430         FILE *fp;
431         int i, part_size;
432         char dev[PATH_MAX];
433         char *path = NULL;
434         struct stat s;
435
436         if ((fp = fopen("/proc/mtd", "r")))
437         {
438                 while( fgets(dev, sizeof(dev), fp) )
439                 {
440                         if( strstr(dev, "nvram") && sscanf(dev, "mtd%d: %08x", &i, &part_size) )
441                         {
442                                 nvram_part_size = part_size;
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                 fclose(fp);
456         }
457
458         return path;
459 }
460
461 /* Check NVRAM staging file. */
462 char * nvram_find_staging(void)
463 {
464         struct stat s;
465
466         if( (stat(NVRAM_STAGING, &s) > -1) && (s.st_mode & S_IFREG) )
467         {
468                 return NVRAM_STAGING;
469         }
470
471         return NULL;
472 }
473
474 /* Copy NVRAM contents to staging file. */
475 int nvram_to_staging(void)
476 {
477         int fdmtd, fdstg, stat;
478         char *mtd = nvram_find_mtd();
479         char buf[nvram_part_size];
480
481         stat = -1;
482
483         if( (mtd != NULL) && (nvram_part_size > 0) )
484         {
485                 if( (fdmtd = open(mtd, O_RDONLY)) > -1 )
486                 {
487                         if( read(fdmtd, buf, sizeof(buf)) == sizeof(buf) )
488                         {
489                                 if((fdstg = open(NVRAM_STAGING, O_WRONLY | O_CREAT, 0600)) > -1)
490                                 {
491                                         write(fdstg, buf, sizeof(buf));
492                                         fsync(fdstg);
493                                         close(fdstg);
494
495                                         stat = 0;
496                                 }
497                         }
498
499                         close(fdmtd);
500                 }
501         }
502
503         free(mtd);
504         return stat;
505 }
506
507 /* Copy staging file to NVRAM device. */
508 int staging_to_nvram(void)
509 {
510         int fdmtd, fdstg, stat;
511         char *mtd = nvram_find_mtd();
512         char buf[nvram_part_size];
513
514         stat = -1;
515
516         if( (mtd != NULL) && (nvram_part_size > 0) )
517         {
518                 if( (fdstg = open(NVRAM_STAGING, O_RDONLY)) > -1 )
519                 {
520                         if( read(fdstg, buf, sizeof(buf)) == sizeof(buf) )
521                         {
522                                 if( (fdmtd = open(mtd, O_WRONLY | O_SYNC)) > -1 )
523                                 {
524                                         write(fdmtd, buf, sizeof(buf));
525                                         fsync(fdmtd);
526                                         close(fdmtd);
527                                         stat = 0;
528                                 }
529                         }
530
531                         close(fdstg);
532
533                         if( !stat )
534                                 stat = unlink(NVRAM_STAGING) ? 1 : 0;
535                 }
536         }
537
538         free(mtd);
539         return stat;
540 }