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 */
22size_t nvram_part_size = 0;
23
24
25/*
26 * -- Helper functions --
27 */
28
29/* String hash */
30static 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. */
41static 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. */
65static 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. */
96static 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. */
144nvram_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. */
150char * 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. */
171int 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. */
206int 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. */
232nvram_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. */
259int 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. */
339nvram_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. */
417int 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. */
428char * 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. */
462char * 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. */
475int 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. */
508int 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}
541