xref: /freebsd/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c (revision af23369a6deaaeb612ab266eb88b8bb8d560c322)
1 /*-
2  * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/param.h>
34 #include <sys/endian.h>
35 
36 #ifdef _KERNEL
37 
38 #include <sys/bus.h>
39 #include <sys/ctype.h>
40 #include <sys/malloc.h>
41 #include <sys/systm.h>
42 
43 #else /* !_KERNEL */
44 
45 #include <ctype.h>
46 #include <stdint.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 
51 #endif /* _KERNEL */
52 
53 #include "bhnd_nvram_private.h"
54 
55 #include "bhnd_nvram_datavar.h"
56 
57 #include "bhnd_nvram_data_bcmreg.h"
58 #include "bhnd_nvram_data_bcmvar.h"
59 
60 /*
61  * Broadcom NVRAM data class.
62  *
63  * The Broadcom NVRAM NUL-delimited ASCII format is used by most
64  * Broadcom SoCs.
65  *
66  * The NVRAM data is encoded as a standard header, followed by series of
67  * NUL-terminated 'key=value' strings; the end of the stream is denoted
68  * by a single extra NUL character.
69  */
70 
71 struct bhnd_nvram_bcm;
72 
73 static struct bhnd_nvram_bcm_hvar	*bhnd_nvram_bcm_gethdrvar(
74 					     struct bhnd_nvram_bcm *bcm,
75 					     const char *name);
76 static struct bhnd_nvram_bcm_hvar	*bhnd_nvram_bcm_to_hdrvar(
77 					     struct bhnd_nvram_bcm *bcm,
78 					     void *cookiep);
79 static size_t				 bhnd_nvram_bcm_hdrvar_index(
80 					     struct bhnd_nvram_bcm *bcm,
81 					     struct bhnd_nvram_bcm_hvar *hvar);
82 /*
83  * Set of BCM NVRAM header values that are required to be mirrored in the
84  * NVRAM data itself.
85  *
86  * If they're not included in the parsed NVRAM data, we need to vend the
87  * header-parsed values with their appropriate keys, and add them in any
88  * updates to the NVRAM data.
89  *
90  * If they're modified in NVRAM, we need to sync the changes with the
91  * the NVRAM header values.
92  */
93 static const struct bhnd_nvram_bcm_hvar bhnd_nvram_bcm_hvars[] = {
94 	{
95 		.name	= BCM_NVRAM_CFG0_SDRAM_INIT_VAR,
96 		.type	= BHND_NVRAM_TYPE_UINT16,
97 		.len	= sizeof(uint16_t),
98 		.nelem	= 1,
99 	},
100 	{
101 		.name	= BCM_NVRAM_CFG1_SDRAM_CFG_VAR,
102 		.type	= BHND_NVRAM_TYPE_UINT16,
103 		.len	= sizeof(uint16_t),
104 		.nelem	= 1,
105 	},
106 	{
107 		.name	= BCM_NVRAM_CFG1_SDRAM_REFRESH_VAR,
108 		.type	= BHND_NVRAM_TYPE_UINT16,
109 		.len	= sizeof(uint16_t),
110 		.nelem	= 1,
111 	},
112 	{
113 		.name	= BCM_NVRAM_SDRAM_NCDL_VAR,
114 		.type	= BHND_NVRAM_TYPE_UINT32,
115 		.len	= sizeof(uint32_t),
116 		.nelem	= 1,
117 	},
118 };
119 
120 /** BCM NVRAM data class instance */
121 struct bhnd_nvram_bcm {
122 	struct bhnd_nvram_data		 nv;	/**< common instance state */
123 	struct bhnd_nvram_io		*data;	/**< backing buffer */
124 	bhnd_nvram_plist		*opts;	/**< serialization options */
125 
126 	/** BCM header values */
127 	struct bhnd_nvram_bcm_hvar	 hvars[nitems(bhnd_nvram_bcm_hvars)];
128 
129 	size_t				 count;	/**< total variable count */
130 };
131 
132 BHND_NVRAM_DATA_CLASS_DEFN(bcm, "Broadcom", BHND_NVRAM_DATA_CAP_DEVPATHS,
133     sizeof(struct bhnd_nvram_bcm))
134 
135 static int
136 bhnd_nvram_bcm_probe(struct bhnd_nvram_io *io)
137 {
138 	struct bhnd_nvram_bcmhdr	hdr;
139 	int				error;
140 
141 	if ((error = bhnd_nvram_io_read(io, 0x0, &hdr, sizeof(hdr))))
142 		return (error);
143 
144 	if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
145 		return (ENXIO);
146 
147 	if (le32toh(hdr.size) > bhnd_nvram_io_getsize(io))
148 		return (ENXIO);
149 
150 	return (BHND_NVRAM_DATA_PROBE_DEFAULT);
151 }
152 
153 /**
154  * Parser states for bhnd_nvram_bcm_getvar_direct_common().
155  */
156 typedef enum {
157 	BCM_PARSE_KEY_START,
158 	BCM_PARSE_KEY_CONT,
159 	BCM_PARSE_KEY,
160 	BCM_PARSE_NEXT_KEY,
161 	BCM_PARSE_VALUE_START,
162 	BCM_PARSE_VALUE
163 } bcm_parse_state;
164 
165 static int
166 bhnd_nvram_bcm_getvar_direct(struct bhnd_nvram_io *io, const char *name,
167     void *outp, size_t *olen, bhnd_nvram_type otype)
168 {
169 	return (bhnd_nvram_bcm_getvar_direct_common(io, name, outp, olen, otype,
170 	    true));
171 }
172 
173 /**
174  * Common BCM/BCMRAW implementation of bhnd_nvram_getvar_direct().
175  */
176 int
177 bhnd_nvram_bcm_getvar_direct_common(struct bhnd_nvram_io *io, const char *name,
178     void *outp, size_t *olen, bhnd_nvram_type otype, bool have_header)
179 {
180 	struct bhnd_nvram_bcmhdr	 hdr;
181 	char				 buf[512];
182 	bcm_parse_state			 pstate;
183 	size_t				 limit, offset;
184 	size_t				 buflen, bufpos;
185 	size_t				 namelen, namepos;
186 	size_t				 vlen;
187 	int				 error;
188 
189 	limit = bhnd_nvram_io_getsize(io);
190 	offset = 0;
191 
192 	/* Fetch and validate the header */
193 	if (have_header) {
194 		if ((error = bhnd_nvram_io_read(io, offset, &hdr, sizeof(hdr))))
195 			return (error);
196 
197 		if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
198 			return (ENXIO);
199 
200 		offset += sizeof(hdr);
201 		limit = bhnd_nv_ummin(le32toh(hdr.size), limit);
202 	}
203 
204 	/* Loop our parser until we find the requested variable, or hit EOF */
205 	pstate = BCM_PARSE_KEY_START;
206 	buflen = 0;
207 	bufpos = 0;
208 	namelen = strlen(name);
209 	namepos = 0;
210 	vlen = 0;
211 
212 	while ((offset - bufpos) < limit) {
213 		BHND_NV_ASSERT(bufpos <= buflen,
214 		    ("buf position invalid (%zu > %zu)", bufpos, buflen));
215 		BHND_NV_ASSERT(buflen <= sizeof(buf),
216 		    ("buf length invalid (%zu > %zu", buflen, sizeof(buf)));
217 
218 		/* Repopulate our parse buffer? */
219 		if (buflen - bufpos == 0) {
220 			BHND_NV_ASSERT(offset < limit, ("offset overrun"));
221 
222 			buflen = bhnd_nv_ummin(sizeof(buf), limit - offset);
223 			bufpos = 0;
224 
225 			error = bhnd_nvram_io_read(io, offset, buf, buflen);
226 			if (error)
227 				return (error);
228 
229 			offset += buflen;
230 		}
231 
232 		switch (pstate) {
233 		case BCM_PARSE_KEY_START:
234 			BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!"));
235 
236 			/* An extra '\0' denotes NVRAM EOF */
237 			if (buf[bufpos] == '\0')
238 				return (ENOENT);
239 
240 			/* Reset name matching position */
241 			namepos = 0;
242 
243 			/* Start name matching */
244 			pstate = BCM_PARSE_KEY_CONT;
245 			break;
246 
247 		case BCM_PARSE_KEY_CONT: {
248 			size_t navail, nleft;
249 
250 			nleft = namelen - namepos;
251 			navail = bhnd_nv_ummin(buflen - bufpos, nleft);
252 
253 			if (strncmp(name+namepos, buf+bufpos, navail) == 0) {
254 				/* Matched */
255 				namepos += navail;
256 				bufpos += navail;
257 
258 				/* If we've matched the full variable name,
259 				 * look for its trailing delimiter */
260 				if (namepos == namelen)
261 					pstate = BCM_PARSE_KEY;
262 			} else {
263 				/* No match; advance to next entry and restart
264 				 * name matching */
265 				pstate = BCM_PARSE_NEXT_KEY;
266 			}
267 
268 			break;
269 		}
270 
271 		case BCM_PARSE_KEY:
272 			BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!"));
273 
274 			if (buf[bufpos] == '=') {
275 				/* Key fully matched; advance past '=' and
276 				 * parse the value */
277 				bufpos++;
278 				pstate = BCM_PARSE_VALUE_START;
279 			} else {
280 				/* No match; advance to next entry and restart
281 				 * name matching */
282 				pstate = BCM_PARSE_NEXT_KEY;
283 			}
284 
285 			break;
286 
287 		case BCM_PARSE_NEXT_KEY: {
288 			const char *p;
289 
290 			/* Scan for a '\0' terminator */
291 			p = memchr(buf+bufpos, '\0', buflen - bufpos);
292 
293 			if (p != NULL) {
294 				/* Found entry terminator; restart name
295 				 * matching at next entry */
296 				pstate = BCM_PARSE_KEY_START;
297 				bufpos = (p - buf) + 1 /* skip '\0' */;
298 			} else {
299 				/* Consumed full buffer looking for '\0';
300 				 * force repopulation of the buffer and
301 				 * retry */
302 				bufpos = buflen;
303 			}
304 
305 			break;
306 		}
307 
308 		case BCM_PARSE_VALUE_START: {
309 			const char *p;
310 
311 			/* Scan for a '\0' terminator */
312 			p = memchr(buf+bufpos, '\0', buflen - bufpos);
313 
314 			if (p != NULL) {
315 				/* Found entry terminator; parse the value */
316 				vlen = p - &buf[bufpos];
317 				pstate = BCM_PARSE_VALUE;
318 
319 			} else if (p == NULL && offset == limit) {
320 				/* Hit EOF without a terminating '\0';
321 				 * treat the entry as implicitly terminated */
322 				vlen = buflen - bufpos;
323 				pstate = BCM_PARSE_VALUE;
324 
325 			} else if (p == NULL && bufpos > 0) {
326 				size_t	nread;
327 
328 				/* Move existing value data to start of
329 				 * buffer */
330 				memmove(buf, buf+bufpos, buflen - bufpos);
331 				buflen = bufpos;
332 				bufpos = 0;
333 
334 				/* Populate full buffer to allow retry of
335 				 * value parsing */
336 				nread = bhnd_nv_ummin(sizeof(buf) - buflen,
337 				    limit - offset);
338 
339 				error = bhnd_nvram_io_read(io, offset,
340 				    buf+buflen, nread);
341 				if (error)
342 					return (error);
343 
344 				offset += nread;
345 				buflen += nread;
346 			} else {
347 				/* Value exceeds our buffer capacity */
348 				BHND_NV_LOG("cannot parse value for '%s' "
349 				    "(exceeds %zu byte limit)\n", name,
350 				    sizeof(buf));
351 
352 				return (ENXIO);
353 			}
354 
355 			break;
356 		}
357 
358 		case BCM_PARSE_VALUE:
359 			BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun"));
360 
361 			return (bhnd_nvram_value_coerce(buf+bufpos, vlen,
362 			    BHND_NVRAM_TYPE_STRING, outp, olen, otype));
363 		}
364 	}
365 
366 	/* Variable not found */
367 	return (ENOENT);
368 }
369 
370 static int
371 bhnd_nvram_bcm_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
372     bhnd_nvram_plist *options, void *outp, size_t *olen)
373 {
374 	struct bhnd_nvram_bcmhdr	 hdr;
375 	bhnd_nvram_prop			*prop;
376 	size_t				 limit, nbytes;
377 	uint32_t			 sdram_ncdl;
378 	uint16_t			 sdram_init, sdram_cfg, sdram_refresh;
379 	uint8_t				 bcm_ver, crc8;
380 	int				 error;
381 
382 	/* Determine output byte limit */
383 	if (outp != NULL)
384 		limit = *olen;
385 	else
386 		limit = 0;
387 
388 	/* Fetch required header variables */
389 #define	PROPS_GET_HDRVAR(_name, _dest, _type)	do {			\
390 		const char *name = BCM_NVRAM_ ## _name ## _VAR;	\
391 		if (!bhnd_nvram_plist_contains(props, name)) {		\
392 			BHND_NV_LOG("missing required property: %s\n",	\
393 			    name);					\
394 			return (EFTYPE);				\
395 		}							\
396 									\
397 		error = bhnd_nvram_plist_get_encoded(props, name,	\
398 		    (_dest), sizeof(*(_dest)),				\
399 		    BHND_NVRAM_TYPE_ ##_type);				\
400 		if (error) {						\
401 			BHND_NV_LOG("error reading required header "	\
402 			    "%s property: %d\n", name, error);		\
403 			return (EFTYPE);				\
404 		}							\
405 } while (0)
406 
407 	PROPS_GET_HDRVAR(SDRAM_NCDL,		&sdram_ncdl,	UINT32);
408 	PROPS_GET_HDRVAR(CFG0_SDRAM_INIT,	&sdram_init,	UINT16);
409 	PROPS_GET_HDRVAR(CFG1_SDRAM_CFG,	&sdram_cfg,	UINT16);
410 	PROPS_GET_HDRVAR(CFG1_SDRAM_REFRESH,	&sdram_refresh,	UINT16);
411 
412 #undef	PROPS_GET_HDRVAR
413 
414 	/* Fetch BCM nvram version from options */
415 	if (options != NULL &&
416 	    bhnd_nvram_plist_contains(options, BCM_NVRAM_ENCODE_OPT_VERSION))
417 	{
418 		error = bhnd_nvram_plist_get_uint8(options,
419 		    BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver);
420 		if (error) {
421 			BHND_NV_LOG("error reading %s uint8 option value: %d\n",
422 			    BCM_NVRAM_ENCODE_OPT_VERSION, error);
423 			return (EINVAL);
424 		}
425 	} else {
426 		bcm_ver = BCM_NVRAM_CFG0_VER_DEFAULT;
427 	}
428 
429 	/* Construct our header */
430 	hdr = (struct bhnd_nvram_bcmhdr) {
431 		.magic = htole32(BCM_NVRAM_MAGIC),
432 		.size = 0,
433 		.cfg0 = 0,
434 		.cfg1 = 0,
435 		.sdram_ncdl = htole32(sdram_ncdl)
436 	};
437 
438 	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, 0x0);
439 	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER, bcm_ver);
440 	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_SDRAM_INIT,
441 	    htole16(sdram_init));
442 
443 	hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_CFG,
444 	    htole16(sdram_cfg));
445 	hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_REFRESH,
446 	    htole16(sdram_refresh));
447 
448 	/* Write the header */
449 	nbytes = sizeof(hdr);
450 	if (limit >= nbytes)
451 		memcpy(outp, &hdr, sizeof(hdr));
452 
453 	/* Write all properties */
454 	prop = NULL;
455 	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
456 		const char	*name;
457 		char		*p;
458 		size_t		 prop_limit;
459 		size_t		 name_len, value_len;
460 
461 		if (outp == NULL || limit < nbytes) {
462 			p = NULL;
463 			prop_limit = 0;
464 		} else {
465 			p = ((char *)outp) + nbytes;
466 			prop_limit = limit - nbytes;
467 		}
468 
469 		/* Fetch and write name + '=' to output */
470 		name = bhnd_nvram_prop_name(prop);
471 		name_len = strlen(name) + 1;
472 
473 		if (prop_limit > name_len) {
474 			memcpy(p, name, name_len - 1);
475 			p[name_len - 1] = '=';
476 
477 			prop_limit -= name_len;
478 			p += name_len;
479 		} else {
480 			prop_limit = 0;
481 			p = NULL;
482 		}
483 
484 		/* Advance byte count */
485 		if (SIZE_MAX - nbytes < name_len)
486 			return (EFTYPE); /* would overflow size_t */
487 
488 		nbytes += name_len;
489 
490 		/* Attempt to write NUL-terminated value to output */
491 		value_len = prop_limit;
492 		error = bhnd_nvram_prop_encode(prop, p, &value_len,
493 		    BHND_NVRAM_TYPE_STRING);
494 
495 		/* If encoding failed for any reason other than ENOMEM (which
496 		 * we'll detect and report after encoding all properties),
497 		 * return immediately */
498 		if (error && error != ENOMEM) {
499 			BHND_NV_LOG("error serializing %s to required type "
500 			    "%s: %d\n", name,
501 			    bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
502 			    error);
503 			return (error);
504 		}
505 
506 		/* Advance byte count */
507 		if (SIZE_MAX - nbytes < value_len)
508 			return (EFTYPE); /* would overflow size_t */
509 
510 		nbytes += value_len;
511 	}
512 
513 	/* Write terminating '\0' */
514 	if (limit > nbytes)
515 		*((char *)outp + nbytes) = '\0';
516 
517 	if (nbytes == SIZE_MAX)
518 		return (EFTYPE); /* would overflow size_t */
519 	else
520 		nbytes++;
521 
522 	/* Update header length; this must fit within the header's 32-bit size
523 	 * field */
524 	if (nbytes <= UINT32_MAX) {
525 		hdr.size = (uint32_t)nbytes;
526 	} else {
527 		BHND_NV_LOG("size %zu exceeds maximum supported size of %u "
528 		    "bytes\n", nbytes, UINT32_MAX);
529 		return (EFTYPE);
530 	}
531 
532 	/* Provide required length */
533 	*olen = nbytes;
534 	if (limit < *olen) {
535 		if (outp == NULL)
536 			return (0);
537 
538 		return (ENOMEM);
539 	}
540 
541 	/* Calculate the CRC value */
542 	BHND_NV_ASSERT(nbytes >= BCM_NVRAM_CRC_SKIP, ("invalid output size"));
543 	crc8 = bhnd_nvram_crc8((uint8_t *)outp + BCM_NVRAM_CRC_SKIP,
544 	    nbytes - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL);
545 
546 	/* Update CRC and write the finalized header */
547 	BHND_NV_ASSERT(nbytes >= sizeof(hdr), ("invalid output size"));
548 	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, crc8);
549 	memcpy(outp, &hdr, sizeof(hdr));
550 
551 	return (0);
552 }
553 
554 /**
555  * Initialize @p bcm with the provided NVRAM data mapped by @p src.
556  *
557  * @param bcm A newly allocated data instance.
558  */
559 static int
560 bhnd_nvram_bcm_init(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_io *src)
561 {
562 	struct bhnd_nvram_bcmhdr	 hdr;
563 	uint8_t				*p;
564 	void				*ptr;
565 	size_t				 io_offset, io_size;
566 	uint8_t				 crc, valid, bcm_ver;
567 	int				 error;
568 
569 	if ((error = bhnd_nvram_io_read(src, 0x0, &hdr, sizeof(hdr))))
570 		return (error);
571 
572 	if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
573 		return (ENXIO);
574 
575 	/* Fetch the actual NVRAM image size */
576 	io_size = le32toh(hdr.size);
577 	if (io_size < sizeof(hdr)) {
578 		/* The header size must include the header itself */
579 		BHND_NV_LOG("corrupt header size: %zu\n", io_size);
580 		return (EINVAL);
581 	}
582 
583 	if (io_size > bhnd_nvram_io_getsize(src)) {
584 		BHND_NV_LOG("header size %zu exceeds input size %zu\n",
585 		    io_size, bhnd_nvram_io_getsize(src));
586 		return (EINVAL);
587 	}
588 
589 	/* Allocate a buffer large enough to hold the NVRAM image, and
590 	 * an extra EOF-signaling NUL (on the chance it's missing from the
591 	 * source data) */
592 	if (io_size == SIZE_MAX)
593 		return (ENOMEM);
594 
595 	bcm->data = bhnd_nvram_iobuf_empty(io_size, io_size + 1);
596 	if (bcm->data == NULL)
597 		return (ENOMEM);
598 
599 	/* Fetch a pointer into our backing buffer and copy in the
600 	 * NVRAM image. */
601 	error = bhnd_nvram_io_write_ptr(bcm->data, 0x0, &ptr, io_size, NULL);
602 	if (error)
603 		return (error);
604 
605 	p = ptr;
606 	if ((error = bhnd_nvram_io_read(src, 0x0, p, io_size)))
607 		return (error);
608 
609 	/* Verify the CRC */
610 	valid = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC);
611 	crc = bhnd_nvram_crc8(p + BCM_NVRAM_CRC_SKIP,
612 	    io_size - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL);
613 
614 	if (crc != valid) {
615 		BHND_NV_LOG("warning: NVRAM CRC error (crc=%#hhx, "
616 		    "expected=%hhx)\n", crc, valid);
617 	}
618 
619 	/* Populate header variable definitions */
620 #define	BCM_READ_HDR_VAR(_name, _dest, _swap) do {		\
621 	struct bhnd_nvram_bcm_hvar *data;				\
622 	data = bhnd_nvram_bcm_gethdrvar(bcm, _name ##_VAR);		\
623 	BHND_NV_ASSERT(data != NULL,						\
624 	    ("no such header variable: " __STRING(_name)));		\
625 									\
626 									\
627 	data->value. _dest = _swap(BCM_NVRAM_GET_BITS(			\
628 	    hdr. _name ## _FIELD, _name));				\
629 } while(0)
630 
631 	BCM_READ_HDR_VAR(BCM_NVRAM_CFG0_SDRAM_INIT,	u16, le16toh);
632 	BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_CFG,	u16, le16toh);
633 	BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_REFRESH,	u16, le16toh);
634 	BCM_READ_HDR_VAR(BCM_NVRAM_SDRAM_NCDL,		u32, le32toh);
635 
636 	_Static_assert(nitems(bcm->hvars) == 4, "missing initialization for"
637 	    "NVRAM header variable(s)");
638 
639 #undef BCM_READ_HDR_VAR
640 
641 	/* Process the buffer */
642 	bcm->count = 0;
643 	io_offset = sizeof(hdr);
644 	while (io_offset < io_size) {
645 		char		*envp;
646 		const char	*name, *value;
647 		size_t		 envp_len;
648 		size_t		 name_len, value_len;
649 
650 		/* Parse the key=value string */
651 		envp = (char *) (p + io_offset);
652 		envp_len = strnlen(envp, io_size - io_offset);
653 		error = bhnd_nvram_parse_env(envp, envp_len, '=', &name,
654 					     &name_len, &value, &value_len);
655 		if (error) {
656 			BHND_NV_LOG("error parsing envp at offset %#zx: %d\n",
657 			    io_offset, error);
658 			return (error);
659 		}
660 
661 		/* Insert a '\0' character, replacing the '=' delimiter and
662 		 * allowing us to vend references directly to the variable
663 		 * name */
664 		*(envp + name_len) = '\0';
665 
666 		/* Record any NVRAM variables that mirror our header variables.
667 		 * This is a brute-force search -- for the amount of data we're
668 		 * operating on, it shouldn't be an issue. */
669 		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
670 			struct bhnd_nvram_bcm_hvar	*hvar;
671 			union bhnd_nvram_bcm_hvar_value	 hval;
672 			size_t				 hval_len;
673 
674 			hvar = &bcm->hvars[i];
675 
676 			/* Already matched? */
677 			if (hvar->envp != NULL)
678 				continue;
679 
680 			/* Name matches? */
681 			if ((strcmp(name, hvar->name)) != 0)
682 				continue;
683 
684 			/* Save pointer to mirrored envp */
685 			hvar->envp = envp;
686 
687 			/* Check for stale value */
688 			hval_len = sizeof(hval);
689 			error = bhnd_nvram_value_coerce(value, value_len,
690 			    BHND_NVRAM_TYPE_STRING, &hval, &hval_len,
691 			    hvar->type);
692 			if (error) {
693 				/* If parsing fails, we can likely only make
694 				 * things worse by trying to synchronize the
695 				 * variables */
696 				BHND_NV_LOG("error parsing header variable "
697 				    "'%s=%s': %d\n", name, value, error);
698 			} else if (hval_len != hvar->len) {
699 				hvar->stale = true;
700 			} else if (memcmp(&hval, &hvar->value, hval_len) != 0) {
701 				hvar->stale = true;
702 			}
703 		}
704 
705 		/* Seek past the value's terminating '\0' */
706 		io_offset += envp_len;
707 		if (io_offset == io_size) {
708 			BHND_NV_LOG("missing terminating NUL at offset %#zx\n",
709 			    io_offset);
710 			return (EINVAL);
711 		}
712 
713 		if (*(p + io_offset) != '\0') {
714 			BHND_NV_LOG("invalid terminator '%#hhx' at offset "
715 			    "%#zx\n", *(p + io_offset), io_offset);
716 			return (EINVAL);
717 		}
718 
719 		/* Update variable count */
720 		bcm->count++;
721 
722 		/* Seek to the next record */
723 		if (++io_offset == io_size) {
724 			char ch;
725 
726 			/* Hit EOF without finding a terminating NUL
727 			 * byte; we need to grow our buffer and append
728 			 * it */
729 			io_size++;
730 			if ((error = bhnd_nvram_io_setsize(bcm->data, io_size)))
731 				return (error);
732 
733 			/* Write NUL byte */
734 			ch = '\0';
735 			error = bhnd_nvram_io_write(bcm->data, io_size-1, &ch,
736 			    sizeof(ch));
737 			if (error)
738 				return (error);
739 		}
740 
741 		/* Check for explicit EOF (encoded as a single empty NUL
742 		 * terminated string) */
743 		if (*(p + io_offset) == '\0')
744 			break;
745 	}
746 
747 	/* Add non-mirrored header variables to total count variable */
748 	for (size_t i = 0; i < nitems(bcm->hvars); i++) {
749 		if (bcm->hvars[i].envp == NULL)
750 			bcm->count++;
751 	}
752 
753 	/* Populate serialization options from our header */
754 	bcm_ver = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER);
755 	error = bhnd_nvram_plist_append_bytes(bcm->opts,
756 	    BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver, sizeof(bcm_ver),
757 	    BHND_NVRAM_TYPE_UINT8);
758 	if (error)
759 		return (error);
760 
761 	return (0);
762 }
763 
764 static int
765 bhnd_nvram_bcm_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
766 {
767 	struct bhnd_nvram_bcm	*bcm;
768 	int			 error;
769 
770 	bcm = (struct bhnd_nvram_bcm *)nv;
771 
772 	/* Populate default BCM mirrored header variable set */
773 	_Static_assert(sizeof(bcm->hvars) == sizeof(bhnd_nvram_bcm_hvars),
774 	    "hvar declarations must match bhnd_nvram_bcm_hvars template");
775 	memcpy(bcm->hvars, bhnd_nvram_bcm_hvars, sizeof(bcm->hvars));
776 
777 	/* Allocate (empty) option list, to be populated by
778 	 * bhnd_nvram_bcm_init() */
779 	bcm->opts = bhnd_nvram_plist_new();
780 	if (bcm->opts == NULL)
781 		return (ENOMEM);
782 
783 	/* Parse the BCM input data and initialize our backing
784 	 * data representation */
785 	if ((error = bhnd_nvram_bcm_init(bcm, io))) {
786 		bhnd_nvram_bcm_free(nv);
787 		return (error);
788 	}
789 
790 	return (0);
791 }
792 
793 static void
794 bhnd_nvram_bcm_free(struct bhnd_nvram_data *nv)
795 {
796 	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
797 
798 	if (bcm->data != NULL)
799 		bhnd_nvram_io_free(bcm->data);
800 
801 	if (bcm->opts != NULL)
802 		bhnd_nvram_plist_release(bcm->opts);
803 }
804 
805 size_t
806 bhnd_nvram_bcm_count(struct bhnd_nvram_data *nv)
807 {
808 	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
809 	return (bcm->count);
810 }
811 
812 static bhnd_nvram_plist *
813 bhnd_nvram_bcm_options(struct bhnd_nvram_data *nv)
814 {
815 	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
816 	return (bcm->opts);
817 }
818 
819 static uint32_t
820 bhnd_nvram_bcm_caps(struct bhnd_nvram_data *nv)
821 {
822 	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
823 }
824 
825 static const char *
826 bhnd_nvram_bcm_next(struct bhnd_nvram_data *nv, void **cookiep)
827 {
828 	struct bhnd_nvram_bcm		*bcm;
829 	struct bhnd_nvram_bcm_hvar	*hvar, *hvar_next;
830 	const void			*ptr;
831 	const char			*envp, *basep;
832 	size_t				 io_size, io_offset;
833 	int				 error;
834 
835 	bcm = (struct bhnd_nvram_bcm *)nv;
836 
837 	io_offset = sizeof(struct bhnd_nvram_bcmhdr);
838 	io_size = bhnd_nvram_io_getsize(bcm->data) - io_offset;
839 
840 	/* Map backing buffer */
841 	error = bhnd_nvram_io_read_ptr(bcm->data, io_offset, &ptr, io_size,
842 	    NULL);
843 	if (error) {
844 		BHND_NV_LOG("error mapping backing buffer: %d\n", error);
845 		return (NULL);
846 	}
847 
848 	basep = ptr;
849 
850 	/* If cookiep pointers into our header variable array, handle as header
851 	 * variable iteration. */
852 	hvar = bhnd_nvram_bcm_to_hdrvar(bcm, *cookiep);
853 	if (hvar != NULL) {
854 		size_t idx;
855 
856 		/* Advance to next entry, if any */
857 		idx = bhnd_nvram_bcm_hdrvar_index(bcm, hvar) + 1;
858 
859 		/* Find the next header-defined variable that isn't defined in
860 		 * the NVRAM data, start iteration there */
861 		for (size_t i = idx; i < nitems(bcm->hvars); i++) {
862 			hvar_next = &bcm->hvars[i];
863 			if (hvar_next->envp != NULL && !hvar_next->stale)
864 				continue;
865 
866 			*cookiep = hvar_next;
867 			return (hvar_next->name);
868 		}
869 
870 		/* No further header-defined variables; iteration
871 		 * complete */
872 		return (NULL);
873 	}
874 
875 	/* Handle standard NVRAM data iteration */
876 	if (*cookiep == NULL) {
877 		/* Start at the first NVRAM data record */
878 		envp = basep;
879 	} else {
880 		/* Seek to next record */
881 		envp = *cookiep;
882 		envp += strlen(envp) + 1;	/* key + '\0' */
883 		envp += strlen(envp) + 1;	/* value + '\0' */
884 	}
885 
886 	/*
887 	 * Skip entries that have an existing header variable entry that takes
888 	 * precedence over the NVRAM data value.
889 	 *
890 	 * The header's value will be provided when performing header variable
891 	 * iteration
892 	 */
893 	 while ((size_t)(envp - basep) < io_size && *envp != '\0') {
894 		/* Locate corresponding header variable */
895 		hvar = NULL;
896 		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
897 			if (bcm->hvars[i].envp != envp)
898 				continue;
899 
900 			hvar = &bcm->hvars[i];
901 			break;
902 		}
903 
904 		/* If no corresponding hvar entry, or the entry does not take
905 		 * precedence over this NVRAM value, we can safely return this
906 		 * value as-is. */
907 		if (hvar == NULL || !hvar->stale)
908 			break;
909 
910 		/* Seek to next record */
911 		envp += strlen(envp) + 1;	/* key + '\0' */
912 		envp += strlen(envp) + 1;	/* value + '\0' */
913 	 }
914 
915 	/* On NVRAM data EOF, try switching to header variables */
916 	if ((size_t)(envp - basep) == io_size || *envp == '\0') {
917 		/* Find first valid header variable */
918 		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
919 			if (bcm->hvars[i].envp != NULL)
920 				continue;
921 
922 			*cookiep = &bcm->hvars[i];
923 			return (bcm->hvars[i].name);
924 		}
925 
926 		/* No header variables */
927 		return (NULL);
928 	}
929 
930 	*cookiep = __DECONST(void *, envp);
931 	return (envp);
932 }
933 
934 static void *
935 bhnd_nvram_bcm_find(struct bhnd_nvram_data *nv, const char *name)
936 {
937 	return (bhnd_nvram_data_generic_find(nv, name));
938 }
939 
940 static int
941 bhnd_nvram_bcm_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
942     void *cookiep2)
943 {
944 	struct bhnd_nvram_bcm		*bcm;
945 	struct bhnd_nvram_bcm_hvar	*hvar1, *hvar2;
946 
947 	bcm = (struct bhnd_nvram_bcm *)nv;
948 
949 	hvar1 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep1);
950 	hvar2 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep2);
951 
952 	/* Header variables are always ordered below any variables defined
953 	 * in the BCM data */
954 	if (hvar1 != NULL && hvar2 == NULL) {
955 		return (1);	/* hvar follows non-hvar */
956 	} else if (hvar1 == NULL && hvar2 != NULL) {
957 		return (-1);	/* non-hvar precedes hvar */
958 	}
959 
960 	/* Otherwise, both cookies are either hvars or non-hvars. We can
961 	 * safely fall back on pointer order, which will provide a correct
962 	 * ordering matching the behavior of bhnd_nvram_data_next() for
963 	 * both cases */
964 	if (cookiep1 < cookiep2)
965 		return (-1);
966 
967 	if (cookiep1 > cookiep2)
968 		return (1);
969 
970 	return (0);
971 }
972 
973 static int
974 bhnd_nvram_bcm_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
975     size_t *len, bhnd_nvram_type type)
976 {
977 	return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
978 }
979 
980 static int
981 bhnd_nvram_bcm_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
982     bhnd_nvram_val **value)
983 {
984 	return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
985 }
986 
987 static const void *
988 bhnd_nvram_bcm_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
989     size_t *len, bhnd_nvram_type *type)
990 {
991 	struct bhnd_nvram_bcm		*bcm;
992 	struct bhnd_nvram_bcm_hvar	*hvar;
993 	const char			*envp;
994 
995 	bcm = (struct bhnd_nvram_bcm *)nv;
996 
997 	/* Handle header variables */
998 	if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) {
999 		BHND_NV_ASSERT(bhnd_nvram_value_check_aligned(&hvar->value,
1000 		    hvar->len, hvar->type) == 0, ("value misaligned"));
1001 
1002 		*type = hvar->type;
1003 		*len = hvar->len;
1004 		return (&hvar->value);
1005 	}
1006 
1007 	/* Cookie points to key\0value\0 -- get the value address */
1008 	BHND_NV_ASSERT(cookiep != NULL, ("NULL cookiep"));
1009 
1010 	envp = cookiep;
1011 	envp += strlen(envp) + 1;	/* key + '\0' */
1012 	*len = strlen(envp) + 1;	/* value + '\0' */
1013 	*type = BHND_NVRAM_TYPE_STRING;
1014 
1015 	return (envp);
1016 }
1017 
1018 static const char *
1019 bhnd_nvram_bcm_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
1020 {
1021 	struct bhnd_nvram_bcm		*bcm;
1022 	struct bhnd_nvram_bcm_hvar	*hvar;
1023 
1024 	bcm = (struct bhnd_nvram_bcm *)nv;
1025 
1026 	/* Handle header variables */
1027 	if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) {
1028 		return (hvar->name);
1029 	}
1030 
1031 	/* Cookie points to key\0value\0 */
1032 	return (cookiep);
1033 }
1034 
1035 static int
1036 bhnd_nvram_bcm_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
1037     bhnd_nvram_val *value, bhnd_nvram_val **result)
1038 {
1039 	bhnd_nvram_val	*str;
1040 	int		 error;
1041 
1042 	/* Name (trimmed of any path prefix) must be valid */
1043 	if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
1044 		return (EINVAL);
1045 
1046 	/* Value must be bcm-formatted string */
1047 	error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
1048 	    value, BHND_NVRAM_VAL_DYNAMIC);
1049 	if (error)
1050 		return (error);
1051 
1052 	/* Success. Transfer result ownership to the caller. */
1053 	*result = str;
1054 	return (0);
1055 }
1056 
1057 static int
1058 bhnd_nvram_bcm_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
1059 {
1060 	/* We permit deletion of any variable */
1061 	return (0);
1062 }
1063 
1064 /**
1065  * Return the internal BCM data reference for a header-defined variable
1066  * with @p name, or NULL if none exists.
1067  */
1068 static struct bhnd_nvram_bcm_hvar *
1069 bhnd_nvram_bcm_gethdrvar(struct bhnd_nvram_bcm *bcm, const char *name)
1070 {
1071 	for (size_t i = 0; i < nitems(bcm->hvars); i++) {
1072 		if (strcmp(bcm->hvars[i].name, name) == 0)
1073 			return (&bcm->hvars[i]);
1074 	}
1075 
1076 	/* Not found */
1077 	return (NULL);
1078 }
1079 
1080 /**
1081  * If @p cookiep references a header-defined variable, return the
1082  * internal BCM data reference. Otherwise, returns NULL.
1083  */
1084 static struct bhnd_nvram_bcm_hvar *
1085 bhnd_nvram_bcm_to_hdrvar(struct bhnd_nvram_bcm *bcm, void *cookiep)
1086 {
1087 #ifdef BHND_NVRAM_INVARIANTS
1088 	uintptr_t base, ptr;
1089 #endif
1090 
1091 	/* If the cookie falls within the hvar array, it's a
1092 	 * header variable cookie */
1093 	if (nitems(bcm->hvars) == 0)
1094 		return (NULL);
1095 
1096 	if (cookiep < (void *)&bcm->hvars[0])
1097 		return (NULL);
1098 
1099 	if (cookiep > (void *)&bcm->hvars[nitems(bcm->hvars)-1])
1100 		return (NULL);
1101 
1102 #ifdef BHND_NVRAM_INVARIANTS
1103 	base = (uintptr_t)bcm->hvars;
1104 	ptr = (uintptr_t)cookiep;
1105 
1106 	BHND_NV_ASSERT((ptr - base) % sizeof(bcm->hvars[0]) == 0,
1107 	    ("misaligned hvar pointer %p/%p", cookiep, bcm->hvars));
1108 #endif /* INVARIANTS */
1109 
1110 	return ((struct bhnd_nvram_bcm_hvar *)cookiep);
1111 }
1112 
1113 /**
1114  * Return the index of @p hdrvar within @p bcm's backing hvars array.
1115  */
1116 static size_t
1117 bhnd_nvram_bcm_hdrvar_index(struct bhnd_nvram_bcm *bcm,
1118     struct bhnd_nvram_bcm_hvar *hdrvar)
1119 {
1120 	BHND_NV_ASSERT(bhnd_nvram_bcm_to_hdrvar(bcm, (void *)hdrvar) != NULL,
1121 	    ("%p is not a valid hdrvar reference", hdrvar));
1122 
1123 	return (hdrvar - &bcm->hvars[0]);
1124 }
1125