contrib: add a userspace port of the Broadcom NVRAM kernel api, allows NVRAM manipula...
[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() \
18         printf("%s(%i) in %s()\n", \
19                 __FILE__, __LINE__, __FUNCTION__)
20
21 /*
22  * -- Helper functions --
23  */
24
25 /* String hash */
26 static uint32_t hash(const char *s)
27 {
28         uint32_t hash = 0;
29
30         while (*s)
31                 hash = 31 * hash + *s++;
32
33         return hash;
34 }
35
36 /* Free all tuples. */
37 static void _nvram_free(nvram_handle_t *h)
38 {
39         uint32_t i;
40         nvram_tuple_t *t, *next;
41
42         /* Free hash table */
43         for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) {
44                 for (t = h->nvram_hash[i]; t; t = next) {
45                         next = t->next;
46                         free(t);
47                 }
48                 h->nvram_hash[i] = NULL;
49         }
50
51         /* Free dead table */
52         for (t = h->nvram_dead; t; t = next) {
53                 next = t->next;
54                 free(t);
55         }
56
57         h->nvram_dead = NULL;
58 }
59
60 /* (Re)allocate NVRAM tuples. */
61 static nvram_tuple_t * _nvram_realloc( nvram_handle_t *h, nvram_tuple_t *t,
62         const char *name, const char *value )
63 {
64         if ((strlen(value) + 1) > NVRAM_SPACE)
65                 return NULL;
66
67         if (!t) {
68                 if (!(t = malloc(sizeof(nvram_tuple_t) + strlen(name) + 1)))
69                         return NULL;
70
71                 /* Copy name */
72                 t->name = (char *) &t[1];
73                 strcpy(t->name, name);
74
75                 t->value = NULL;
76         }
77
78         /* Copy value */
79         if (!t->value || strcmp(t->value, value))
80         {
81                 if(!(t->value = (char *) realloc(t->value, strlen(value)+1)))
82                         return NULL;
83
84                 strcpy(t->value, value);
85                 t->value[strlen(value)] = '\0';
86         }
87
88         return t;
89 }
90
91 /* (Re)initialize the hash table. */
92 static int _nvram_rehash(nvram_handle_t *h)
93 {
94         nvram_header_t *header = (nvram_header_t *) &h->mmap[NVRAM_SPACE];
95         char buf[] = "0xXXXXXXXX", *name, *value, *end, *eq;
96
97         /* (Re)initialize hash table */
98         _nvram_free(h);
99
100         /* Parse and set "name=value\0 ... \0\0" */
101         name = (char *) &header[1];
102         /*
103         end = (char *) header + NVRAM_SPACE - 2;
104         end[0] = end[1] = '\0';
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_SPACE];
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
284         /* Leave space for a double NUL at the end */
285         end = (char *) header + NVRAM_SPACE - 2;
286
287         /* Write out all tuples */
288         for (i = 0; i < NVRAM_ARRAYSIZE(h->nvram_hash); i++) {
289                 for (t = h->nvram_hash[i]; t; t = t->next) {
290                         if ((ptr + strlen(t->name) + 1 + strlen(t->value) + 1) > end)
291                                 break;
292                         ptr += sprintf(ptr, "%s=%s", t->name, t->value) + 1;
293                 }
294         }
295
296         /* End with a double NUL */
297         *ptr = '\0';
298         ptr += 2;
299
300         /* Set new length */
301         header->len = NVRAM_ROUNDUP(ptr - (char *) header, 4);
302
303         /* Little-endian CRC8 over the last 11 bytes of the header */
304         tmp.crc_ver_init = htonl(header->crc_ver_init);
305         tmp.config_refresh = htonl(header->config_refresh);
306         tmp.config_ncdl = htonl(header->config_ncdl);
307         crc = hndcrc8((unsigned char *) &tmp + 9, sizeof(nvram_header_t) - 9, 0xff);
308
309         /* Continue CRC8 over data bytes */
310         crc = hndcrc8((unsigned char *) &header[1],
311                 header->len - sizeof(nvram_header_t), crc);
312
313         /* Set new CRC8 */
314         header->crc_ver_init |= crc;
315
316         /* Write out */
317         msync(h->mmap, h->length, MS_SYNC);
318         fsync(h->fd);
319
320         /* Reinitialize hash table */
321         return _nvram_rehash(h);
322 }
323
324 /* Open NVRAM and obtain a handle. */
325 nvram_handle_t * nvram_open(const char *file, int rdonly)
326 {
327         int fd;
328         nvram_handle_t *h;
329         nvram_header_t *header;
330
331         if( (fd = open(file, O_RDWR)) > -1 )
332         {
333                 char *mmap_area = (char *) mmap(
334                         NULL, 0x10000, PROT_READ | PROT_WRITE,
335                         ( rdonly == NVRAM_RO ) ? MAP_PRIVATE : MAP_SHARED, fd, 0);
336
337                 if( mmap_area != MAP_FAILED )
338                 {
339                         memset(mmap_area, 0xFF, NVRAM_SPACE);
340
341                         if((h = (nvram_handle_t *) malloc(sizeof(nvram_handle_t))) != NULL)
342                         {
343                                 memset(h, 0, sizeof(nvram_handle_t));
344
345                                 h->fd     = fd;
346                                 h->mmap   = mmap_area;
347                                 h->length = 0x10000;
348
349                                 header = (nvram_header_t *) &h->mmap[NVRAM_SPACE];
350
351                                 if( header->magic == NVRAM_MAGIC )
352                                 {
353                                         _nvram_rehash(h);
354                                         return h;
355                                 }
356                                 else
357                                 {
358                                         munmap(h->mmap, h->length);
359                                         free(h);
360                                 }
361                         }
362                 }
363         }
364
365         return NULL;
366 }
367
368 /* Close NVRAM and free memory. */
369 int nvram_close(nvram_handle_t *h)
370 {
371         _nvram_free(h);
372         munmap(h->mmap, h->length);
373         close(h->fd);
374         free(h);
375
376         return 0;
377 }
378
379 /* Determine NVRAM device node. */
380 const char * nvram_find_mtd(void)
381 {
382         //return "./samples/nvram.1";
383
384         FILE *fp;
385         int i;
386         char dev[PATH_MAX];
387         char *path;
388
389         // "/dev/mtdblock/" + ( 0 < x < 99 ) + "ro" + \0 = 19
390         if( (path = (char *) malloc(19)) == NULL )
391                 return NULL;
392
393         if ((fp = fopen("/proc/mtd", "r"))) {
394                 while (fgets(dev, sizeof(dev), fp)) {
395                         if (sscanf(dev, "mtd%d:", &i) && strstr(dev, "nvram")) {
396                                 snprintf(path, 19, "/dev/mtdblock/%d", i);
397                         }
398                 }
399                 fclose(fp);
400         }
401
402         return (const char *) path;
403 }
404
405 /* Check NVRAM staging file. */
406 const char * nvram_find_staging(void)
407 {
408         struct stat s;
409
410         if( (stat(NVRAM_STAGING, &s) > -1) && (s.st_mode & S_IFREG) )
411         {
412                 return NVRAM_STAGING;
413         }
414
415         return NULL;
416 }
417
418 /* Copy NVRAM contents to staging file. */
419 int nvram_to_staging(void)
420 {
421         int fdmtd, fdstg, stat;
422         const char *mtd;
423         char buf[0x10000];
424
425         stat = -1;
426
427         if( (mtd = nvram_find_mtd()) != NULL )
428         {
429                 if( (fdmtd = open(mtd, O_RDONLY)) > -1 )
430                 {
431                         if( read(fdmtd, buf, sizeof(buf)) == sizeof(buf) )
432                         {
433                                 if((fdstg = open(NVRAM_STAGING, O_WRONLY | O_CREAT, 0600)) > -1)
434                                 {
435                                         write(fdstg, buf, sizeof(buf));
436                                         fsync(fdstg);
437                                         close(fdstg);
438
439                                         stat = 0;
440                                 }
441                         }
442
443                         close(fdmtd);
444                 }
445         }
446
447         return stat;
448 }
449
450 /* Copy staging file to NVRAM device. */
451 int staging_to_nvram(void)
452 {
453         int fdmtd, fdstg, stat;
454         const char *mtd;
455         char buf[0x10000];
456
457         stat = -1;
458
459         if( (mtd = nvram_find_mtd()) != NULL )
460         {
461                 if( (fdstg = open(NVRAM_STAGING, O_RDONLY)) > -1 )
462                 {
463                         if( read(fdstg, buf, sizeof(buf)) == sizeof(buf) )
464                         {
465                                 if( (fdmtd = open(mtd, O_WRONLY | O_SYNC)) > -1 )
466                                 {
467                                         write(fdmtd, buf, sizeof(buf));
468                                         fsync(fdmtd);
469                                         close(fdmtd);
470                                         stat = 0;
471                                 }
472                         }
473
474                         close(fdstg);
475
476                         if( !stat )
477                                 stat = unlink(NVRAM_STAGING) ? 1 : 0;
478                 }
479         }
480
481         return stat;
482 }